LeetCode 每周算法 5(二叉树)
二叉树算法:
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
WHITE, GRAY = 0, 1
res = []
stack = [(WHITE, root)]
while stack:
color, node = stack.pop()
if node is None: continue
if color == WHITE:
stack.append((WHITE, node.right))
stack.append((GRAY, node))
stack.append((WHITE, node.left))
else:
res.append(node.val)
return res
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int maxDepth(TreeNode* root) {
// 首先检查根节点是否为nullptr
// 如果是nullptr,说明当前子树为空,因此最大深度为0
// 直接返回0,结束递归
if (root == nullptr) return 0;
// 如果根节点不为nullptr,则递归地计算左子树和右子树的最大深度
// maxDepth(root->left) 计算左子树的最大深度
// maxDepth(root->right) 计算右子树的最大深度
// 使用max函数比较这两个值,得到左右子树中较大的深度
// 然后将这个深度加1(加上根节点本身),得到当前子树的最大深度
// 最后返回这个值
return max(maxDepth(root->left), maxDepth(root->right)) + 1;
}
};
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
// 检查根节点是否为nullptr
if (root == nullptr) {
return nullptr;
}
// 递归地反转左子树,并将反转后的左子树的根节点指针存储在left变量中
TreeNode* left = invertTree(root->left);
// 递归地反转右子树,并将反转后的右子树的根节点指针存储在right变量中
TreeNode* right = invertTree(root->right);
// 将当前节点的左子树指针指向原本右子树的位置(即反转后的右子树)
root->left = right;
// 将当前节点的右子树指针指向原本左子树的位置(即反转后的左子树)
root->right = left;
// 返回当前节点,此时它已经成为了反转后子树的根节点
return root;
}
};
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
// 定义一个函数check,用于递归地检查两棵树p和q是否是对称的
// 对称意味着p的左子树与q的右子树结构相同且值相等,同时p的右子树与q的左子树也满足这一条件
bool check(TreeNode *p, TreeNode *q) {
// 如果两个节点都为空,则它们是对称的(因为都没有子树)
if (!p && !q) return true;
// 如果只有一个节点为空,则它们不是对称的
if (!p || !q) return false;
// 递归地检查p的左子树与q的右子树是否对称,以及p的右子树与q的左子树是否对称
// 如果这两对子树都对称,则p和q也是对称的
return p->val == q->val && check(p->left, q->right) && check(p->right, q->left);
}
// 定义一个函数isSymmetric,用于检查一棵二叉树root是否是对称的
// 实际上,对于一棵二叉树来说,要检查它是否对称,只需要检查它的根节点与根节点本身(看似无意义,但这里利用了check函数的特性)
// 这是因为如果整棵树是对称的,那么它的左子树和右子树就应该是镜像对称的
// 所以,我们将根节点作为两棵要比较的树的根传入check函数
bool isSymmetric(TreeNode* root) {
return check(root, root);
}
};
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
int diameter = 0; // 用于存储直径
public:
int maxDepth(TreeNode* root) {
if (root == NULL) {
return 0; // 访问到空节点了,返回0
}
// 计算diameter即L+R+1 并更新diameter
diameter = max(diameter, maxDepth(root->left) + maxDepth(root->right) + 1);
// 返回该节点为根的子树的深度
return max(maxDepth(root->left), maxDepth(root->right)) + 1;
}
int diameterOfBinaryTree(TreeNode* root) {
maxDepth(root);
return diameter - 1;
}
};
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector <vector<int>> result; // 用于存储结果的二维向量
if (!root) { // 如果根节点为空,则直接返回空的结果
return result;
}
queue <TreeNode*> queue; // 使用队列来辅助层序遍历
queue.push(root); // 将根节点入队
while (!queue.empty()) { // 当队列不为空时,继续遍历
int currentLevelSize = queue.size(); // 当前层的节点数
result.push_back(vector <int> ()); // 为当前层创建一个新的向量来存储节点值
for (int i = 1; i <= currentLevelSize; ++i) { // 遍历当前层的所有节点
auto node = queue.front(); queue.pop(); // 取出队列前端的节点,并从队列中移除
result.back().push_back(node->val); // 将节点值添加到当前层的向量中
// 如果当前节点有左子节点,则将左子节点入队
if (node->left) queue.push(node->left);
// 如果当前节点有右子节点,则将右子节点入队
if (node->right) queue.push(node->right);
}
}
return result; // 返回存储了所有层节点值的二维向量
}
};
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:
def helper(left, right):
# 基本情况:如果左边界大于右边界,说明当前子数组为空,返回None作为空树
if left > right:
return None
# 计算中间索引,这里使用了整数除法,以确保结果为整数
mid = (left + right) // 2
# 创建一个新的树节点,其值为中间元素的值
# 注意:这里假设`nums`是一个在函数外部定义的有序数组
root = TreeNode(nums[mid])
# 递归构建左子树,范围是左边界到中间索引的前一个位置
root.left = helper(left, mid - 1)
# 递归构建右子树,范围是中间索引的后一个位置到右边界
root.right = helper(mid + 1, right)
# 返回构建好的当前子树的根节点
return root
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
# 定义一个辅助函数,用于递归地检查BST的有效性
# node是当前检查的节点,lower是该节点允许的最小值(初始为负无穷大),upper是该节点允许的最大值(初始为正无穷大)
def isValidBST(self, root: TreeNode) -> bool:
def helper(node, lower=float('-inf'), upper=float('inf')) -> bool:
# 如果当前节点为空,则认为是有效的BST(空树也是BST)
if not node:
return True
# 获取当前节点的值
val = node.val
# 如果当前节点的值不满足其应在的范围内(即小于等于下限或大于等于上限),则不是有效的BST
if val <= lower or val >= upper:
return False
# 递归检查右子树,右子树的所有节点的值应大于当前节点的值且小于上限
if not helper(node.right, val, upper):
return False
# 递归检查左子树,左子树的所有节点的值应小于当前节点的值且大于等于下限
if not helper(node.left, lower, val):
return False
# 如果以上检查都通过,则当前子树是有效的BST
return True
# 调用辅助函数开始检查整棵树
return helper(root)
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
# 初始化一个空栈用于存储遍历过程中的节点
stack = []
# 当根节点不为空或栈不为空时,继续循环
while root or stack:
# 尽可能地向左子树深入,将沿途的节点推入栈中
while root:
stack.append(root) # 将当前节点推入栈中
root = root.left # 移动到左子节点
# 弹出栈顶元素(即当前子树中最左边的节点)
root = stack.pop()
# 将计数器k减1,因为我们找到了一个元素
k -= 1
# 如果计数器k减至0,说明找到了第k小的元素
if k == 0:
return root.val # 返回该元素的值
# 否则,继续遍历右子树
root = root.right # 移动到右子节点
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<int> rightSideView(TreeNode* root) {
vector<int> ans; // 用于存储每一层最右边的节点值
if (!root) return ans; // 如果根节点为空,则直接返回空向量
queue<TreeNode*> q; // 使用队列进行层序遍历
q.push(root); // 将根节点加入队列
while (!q.empty()) { // 当队列不为空时,继续遍历
int cnt = q.size(); // 当前层的节点数
for (int i = 0; i < cnt; i++) { // 遍历当前层的所有节点
TreeNode* cur = q.front(); q.pop(); // 取出队列的第一个节点,即当前层的某个节点
if (cur->left) q.push(cur->left); // 如果当前节点的左子节点存在,将其加入队列
if (cur->right) q.push(cur->right); // 如果当前节点的右子节点存在,将其加入队列
if (i == cnt - 1) { // 当遍历到当前层的最后一个节点时,将其值加入答案向量
ans.push_back(cur->val);
}
}
}
return ans; // 返回存储了每一层最右边节点值的向量
}
};
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def flatten(self, root: Optional[TreeNode]) -> None:
"""
Do not return anything, modify root in-place instead.
"""
# 初始化一个空列表,用于存储前序遍历的结果
preorderList = list()
# 定义前序遍历的函数
def preorderTraversal(root: TreeNode):
if root: # 如果当前节点不为空
preorderList.append(root) # 将当前节点添加到列表中
preorderTraversal(root.left) # 递归遍历左子树
preorderTraversal(root.right) # 递归遍历右子树
# 调用前序遍历函数,传入根节点
preorderTraversal(root)
# 获取前序遍历结果列表的长度
size = len(preorderList)
# 遍历前序遍历结果列表(从第二个元素开始,即索引为1的元素)
for i in range(1, size):
prev, curr = preorderList[i - 1], preorderList[i] # 分别获取前一个节点和当前节点
prev.left = None # 将前一个节点的左子节点设置为None
prev.right = curr # 将前一个节点的右子节点设置为当前节点
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
# 定义一个内部函数,用于递归地构建二叉树
def myBuildTree(preorder_left: int, preorder_right: int, inorder_left: int, inorder_right: int):
# 如果前序遍历的索引范围无效(即左边界大于右边界),则返回None,表示该子树为空
if preorder_left > preorder_right:
return None
# 前序遍历的第一个元素(当前递归层级的起始索引)是根节点
preorder_root = preorder_left
# 在中序遍历中找到根节点的索引
inorder_root = index[preorder[preorder_root]]
# 创建一个新的TreeNode对象作为根节点
root = TreeNode(preorder[preorder_root])
# 计算左子树的大小(即中序遍历中根节点左侧的元素数量)
size_left_subtree = inorder_root - inorder_left
# 递归构建左子树,并更新前序遍历和中序遍历的索引范围
root.left = myBuildTree(preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1)
# 递归构建右子树,并更新前序遍历和中序遍历的索引范围
root.right = myBuildTree(preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right)
# 返回构建好的子树的根节点
return root
# 获取前序遍历列表的长度
n = len(preorder)
# 创建一个字典,用于将中序遍历中的每个元素映射到其索引
index = {element: i for i, element in enumerate(inorder)}
# 调用内部函数来构建整棵树,传入前序遍历和中序遍历的完整索引范围
return myBuildTree(0, n - 1, 0, n - 1)
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int:
# 使用 defaultdict 初始化 prefix 字典
# 默认值为 0,用于记录从根到当前节点的路径和出现的次数
prefix = defaultdict(int)
# 初始化 prefix[0] 为 1,表示路径和为 0 的情况
#(即不选择任何节点)有 1 种情况(即空路径)
prefix[0] = 1
def dfs(root, curr):
# 如果当前节点为空,则没有路径可以遍历,返回 0
if not root:
return 0
# 初始化当前路径上找到的目标路径数
ret = 0
# 将当前节点的值加到当前路径和 curr 上
curr += root.val
# 如果当前路径和减去目标值 targetSum 在 prefix 中存在
# 则说明之前有一条路径的和加上当前节点的值等于 targetSum
# 因此,我们将这样的路径数加到 ret 中
ret += prefix[curr - targetSum]
# 更新 prefix 字典,表示当前路径和 curr 出现的次数加 1
prefix[curr] += 1
# 递归遍历左子树和右子树,并将返回的路径数加到 ret 中
ret += dfs(root.left, curr)
ret += dfs(root.right, curr)
# 回溯,将当前路径和 curr 的出现次数减 1,以供后续路径使用
prefix[curr] -= 1
# 返回当前节点为根的子树中满足条件的路径总数
return ret
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
# 定义一个内部的DFS函数,用于递归地搜索最低公共祖先
def dfs(root):
# 如果当前节点为空,或者当前节点就是我们要找的节点p或q,则返回当前节点
if not root or root.val in [p.val, q.val]:
return root
# 否则,递归地在左子树中搜索p和q
left = dfs(root.left)
# 递归地在右子树中搜索p和q
right = dfs(root.right)
# 如果左子树和右子树都返回了非空节点,说明p和q分别位于当前节点的左右子树中
# 因此,当前节点就是p和q的最低公共祖先
if left and right:
return root
# 如果左子树返回了非空节点而右子树没有,说明p和q都在左子树中
# 或者其中一个在左子树中,另一个是当前节点(但这种情况已在开头处理)
# 因此,返回左子树中的结果
elif left:
return left
# 同理,如果右子树返回了非空节点而左子树没有,返回右子树中的结果
elif right:
return right
# 如果左右子树都没有返回结果(理论上不会发生,除非树的结构有问题或p, q不在树中)
# 但这里为了代码的完整性,我们仍然保留了这个分支
# 调用DFS函数,从根节点开始搜索
return dfs(root)
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def __init__(self):
# 初始化一个变量来存储找到的最大路径和,初始化为负无穷大
self.maxSum = float("-inf")
def maxPathSum(self, root: Optional[TreeNode]) -> int:
# 定义一个内部递归函数maxGain,用于计算以当前节点为根的子树中,经过当前节点的最大路径和
# 同时,这个函数也会更新全局的maxSum
def maxGain(node):
# 如果当前节点为空,则没有路径和,返回0
if not node:
return 0
# 递归计算左子树和右子树经过各自根节点的最大增益
#(即最大路径和的一部分,但不包括当前节点)
# 如果增益为负,则视为0,因为负增益对路径和没有贡献
leftGain = max(maxGain(node.left), 0)
rightGain = max(maxGain(node.right), 0)
# 计算经过当前节点的路径和(包括当前节点值、左子树增益和右子树增益)
priceNewpath = node.val + leftGain + rightGain
# 更新全局的最大路径和
self.maxSum = max(self.maxSum, priceNewpath)
# 返回以当前节点为根的子树中,经过当前节点的最大单路径和(不包括其他分支)
# 这意味着我们只考虑当前节点、左子树的最大增益或右子树的最大增益中的较大者
return node.val + max(leftGain, rightGain)
# 从根节点开始调用maxGain函数,计算最大路径和
maxGain(root)
# 返回计算得到的最大路径和
return self.maxSum