Leetcode二叉树刷题总结(个人向,代码随想录题单)
二叉树部分刷题章节总结
一,二叉树基础与遍历
- 思路归纳: 递归 & 迭代,DFS & BFS 在二叉树中的实现
- 题单:
二叉树的前序遍历
二叉树的中序遍历
二叉树的后序遍历
二叉树的层序遍历
- 核心代码实现:
二叉树的三种遍历方式(前中后序)的递归实现思路类似,如前序遍历实现的核心代码如下:
def preorder(root): # 根 → 左 → 右
if not root:
return
print(root.val) # 根
preorder(root.left)
preorder(root.right)
中序和后序只需要调整根节点访问位置即可。
而前中后序的迭代法的具体实现方式则各有不同。不过本质都是手动模拟栈/队列
,前序的迭代法核心代码如下:
def preorderTraversal(root: Optional[TreeNode]) -> List[int]:
result = []
myStack = []
if root is not None:
myStack.append(root)
while myStack:
node = myStack.pop()
result.append(node.val)
if node.right is not None:
myStack.append(node.right)
if node.left is not None:
myStack.append(node.left)
return result
后序的迭代写法与前序类似,将前序迭代写法调换入栈顺序,并将结果进行反转即可。
中序的迭代写法与前序/后序的迭代写法不同,原因在于当前遍历的节点与待处理的节点不同
def preorderTraversal(root: Optional[TreeNode]) -> List[int]:
result = []
myStack = []
cur = root
while cur or myStack:
# 一路遍历到最左叶子节点
if cur:
myStack.append(cur)
cur = cur.left
# 从最左叶子节点一路向上,处理中间节点和右节点
else:
cur = myStack.pop()
result = cur.val
cur = cur.right
return result
我们可以通过标志位法统一中序遍历和后续遍历的迭代写法,以下给出中序遍历的模板,后序遍历的标志位法只需要在此基础上改换节点处理顺序即可。我们通过标志位,解决了中序和后序遍历中遍历的节点和待处理节点不相同的问题。由标志位表示一个节点是否应该被 "收割"
def inorderTraversal(root: Optional[TreeNode]) -> List[int]:
result = []
myStack = []
if root is None:
return result
myStack.append([root, False])
while myStack:
node, visited = myStack.pop()
if visited:
result.append(node.val)
continue
if node.right is not None:
myStack.append([node.right, False])
myStack.append([node, True])
if node.left is not None:
myStack.append([node.left, False])
return result
二叉树的层序遍历利用队列存储节点,每次处理一层的所有节点,核心代码如下:
def levelOrder(root: Optional[TreeNode]) -> List[List[int]]:
from collections import deque
result = []
myQueue = deque()
if root is not None:
myQueue.append(root)
while myQueue:
# 队列长度即为当前层元素的数量,逐层进行处理
size = len(myQueue)
levelResult = []
for _ in range(size):
node = myQueue.popleft()
levelResult.append(node.val)
# 将当前节点的左右节点加入到队列中
if node.left:
myQueue.append(node.left)
if node.right:
myQueue.append(node.right)
result.append(levelResult)
return result
- 小结: 遍历是解题套路的入口
- 如果题目涉及 “统计/判断子树属性” → 大概率用后序
- 如果题目涉及 “搜索目标/路径” → 常用前序/DFS
- 如果题目涉及 “顺序性(如二叉搜索树相关问题)” → 常用中序
- 如果题目涉及 “按层分析” → 用层序
二,二叉树的属性问题
- 思路归纳
- 深度/高度:递归返回值就是子树高度 → 典型的自底向上分治 → 后序遍历
- 平衡性:在计算高度的同时,额外返回一个状态(是否平衡)→ 后序遍历
- 对称性:需要同时比较两颗子树 → 自顶向下进行 “双指针递归”
- 题单
二叉树最大深度104
二叉树最小深度111
平衡二叉树110
对称二叉树101
- 核心代码实现
A. 高度类问题(二叉树最大高度/最小高度)
def getDepth(root):
if not root:
return 0
left = getDepth(root.left)
right = getDepth(root.right)
return 1 + max(left, right) # 最大深度
# return 1 + min(left, right) # 最小深度 (注意叶子判断)
B. 平衡类问题(平衡二叉树)
def getHeight(root):
if not root:
return 0
left = getHeight(root.left)
right = getHeight(root.right)
if left == -1 or right == -1 or abs(left - right) > 1:
return -1 # -1 作为“不平衡”标记
return 1 + max(left, right)
def isBalanced(root):
return getHeight(root) != -1
C. 对称性问题(对称二叉树)
def isMirror(p, q):
if not p and not q:
return True
if not p or not q:
return False
return (p.val == q.val and
isMirror(p.left, q.right) and
isMirror(p.right, q.left))
def isSymmetric(root):
if not root:
return True
return isMirror(root.left, root.right)
- 小结
这三个题型的本质:
- 深度 → 返回值为高度
- 平衡 → 返回值为高度 或 标志位
- 对称 → 双指针递归,对应的节点成对比较
三,二叉树的路径相关问题
- 思路归纳
- 路径枚举:DFS 深度优先遍历,携带路径,遇到叶子结点记录 → 典型的回溯法。
- 路径和:递归时传递剩余目标值,直至叶子判断 → Top-down 参数传递。 路径最值:记录遍历过程中符合条件的最优结果(如左下角值)。
- 路径统计:在递归中累积结果,或在回溯时统计。
套路关键词:DFS + 回溯,结合 参数传递 和 条件判断。
- 题单
二叉树所有路径 257
路径总和 112
左叶子之和 404
找树左下角的值 513
- 核心代码实现
A. 路径枚举(二叉树所有路径)
def binaryTreePaths(root):
res, path = [], []
def dfs(node):
if not node:
return
path.append(str(node.val))
if not node.left and not node.right: # 当前节点为空,说明已经形成一条完整的路径
res.append("->".join(path))
else:
dfs(node.left)
dfs(node.right)
path.pop() # 回溯
dfs(root)
return res
B. 路径和
def hasPathSum(root, targetSum):
if not root:
return False
if not root.left and not root.right:
return targetSum == root.val
return (hasPathSum(root.left, targetSum - root.val) or
hasPathSum(root.right, targetSum - root.val))
C. 左叶子之和
def sumOfLeftLeaves(root):
if not root:
return 0
res = 0
if root.left and not root.left.left and not root.left.right:
res += root.left.val
return res + sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right)
D. 左下角的值
def findBottomLeftValue(root):
from collections import deque
q = deque([root])
while q:
node = q.popleft()
# 先右后左,保证最后一个访问的就是最左叶子
if node.right:
q.append(node.right)
if node.left:
q.append(node.left)
return node.val
- 小结
- 枚举路径 → DFS + 回溯,核心是 path.append() / path.pop()。
- 路径和 → 自顶向下参数传递,叶子结点判断。
- 路径最值 → BFS/DFS 均可,用遍历顺序控制结果。
路径类问题的关键就是 遍历到叶子结点时做判断或记录。
四,二叉树的构造与修改
- 思路归纳
- 根据遍历序列构造树:核心是找到 根节点 → 根据遍历数组分割左右子树 → 递归构造。
- 数组转 BST:利用数组有序性,取中点为根,递归左右构建。
- 树的修改/合并:递归对称操作,遇到空节点时特殊处理。
- 翻转树:交换左右子树 → 递归到底。
套路关键词:分治 + 递归构造
- 题单
从中序与后序遍历序列构造二叉树 106
最大二叉树 654
有序数组转化为二叉搜索树 108
合并二叉树 617
翻转二叉树 226
- 核心代码实现
A. 根据中序+后序构造二叉树
def buildTree(inorder, postorder):
if not inorder:
return None
root_val = postorder.pop()
root = TreeNode(root_val)
# 此处仅作为核心代码演示,具体实现时可以传递数组下标以节省资源,而不是为相应数组片段新创建一个数组
idx = inorder.index(root_val)
root.left = buildTree(inorder[:idx], postorder[:idx])
root.right = buildTree(inorder[idx+1:], postorder[idx:])
return root
后序数组的最后一个元素为根节点,根据中序数组的分割,可以分别得到左右子树的中序数组和后序数组,递归构造左右子树即可。
B. 最大二叉树
def constructMaximumBinaryTree(nums):
if not nums:
return None
max_idx = nums.index(max(nums))
# 不断找出最大值分割数组,进行分治
# 同样,具体实现时可以传递数组下标
root = TreeNode(nums[max_idx])
root.left = constructMaximumBinaryTree(nums[:max_idx])
root.right = constructMaximumBinaryTree(nums[max_idx+1:])
return root
C. 有序数组转化为BST
def sortedArrayToBST(nums):
if not nums:
return None
mid = len(nums) // 2
# 根据二叉平衡树的性质,每次只需要选取数组中间的元素作为根节点即可
# 重复该过程递归构造
root = TreeNode(nums[mid])
root.left = sortedArrayToBST(nums[:mid])
root.right = sortedArrayToBST(nums[mid+1:])
return root
D. 合并二叉树
def merge(root1: Optional[TreeNode], root2: Optional[TreeNode]) -> Optional[TreeNode]:
if root1 is None and root2 is None:
return
# 在树1原地对节点的值进行修改
if root1 and root2:
root1.val += root2.val
root1.left = merge(root1.left, root2.left)
root1.right = merge(root1.right, root2.right)
return root1
# 二者仅存其一,直接返回即可
if root1:
return root1
return root2
E. 翻转二叉树
def invertTree(root: Optional[TreeNode]) -> Optional[TreeNode]:
def traverse(cur: Optional[TreeNode]) -> None:
if cur is None:
return
# 交换两个子树后,继续向下递归
cur.left, cur.right = cur.right, cur.left
traverse(cur.left)
traverse(cur.right)
traverse(root)
return root
- 小结
- 构造类问题:本质是分治,找到根 → 分割子树 → 递归构造。
- 修改类问题:递归处理左右子树,注意 空节点边界。
- 翻转、合并这种问题考察的是 递归对称性。
五,二叉搜索树专题
- 思路归纳
- BST 特性:中序遍历有序 → 几乎所有题目都依赖这一性质。
- 二叉搜索树的搜索/插入/删除:递归或迭代,关键在于比较大小后走左/右子树。
- 修剪:利用区间范围,剪去不满足条件的子树。
- 最近公共祖先:利用 BST 有序性,可以快速定位分叉点。
- 最小差值/众数:中序遍历序列有序 → 顺序比较或计数。
- 转化为累加树:倒序中序遍历 + 累加和。
套路关键词:中序有序性 + 递归 / 迭代。
- 题单
二叉搜索树中的搜索 700
二叉搜索树中的插入操作 701
删除二叉搜索树中的节点 450
修剪二叉搜索树 669
二叉搜索树的最近公共祖先 235
二叉搜索树的最小绝对差 530
二叉搜索树中的众数 501
把二叉搜索树转换为累加树 538
- 核心代码实现
A. 二叉搜索树的搜索/插入操作
def searchBST(root, val):
if not root or root.val == val:
return root
if val < root.val:
return searchBST(root.left, val)
else:
return searchBST(root.right, val)
def insertIntoBST(root, val):
if not root:
return TreeNode(val)
if val < root.val:
root.left = insertIntoBST(root.left, val)
else:
root.right = insertIntoBST(root.right, val)
return root
利用BST左小右大的特性,实现快速递归定位
B. 验证二叉搜索树
def isValidBST(root: Optional[TreeNode]) -> bool:
prev = None
def dfs(node: Optional[TreeNode]) -> bool:
nonlocal prev
if not node:
return True
if not dfs(node.left):
return False
# 当前节点小于等于前一个节点,说明当前二叉树不符合二叉搜索树的性质,终止递归
if prev is not None and node.val <= prev:
return False
prev = node.val
return dfs(node.right)
return dfs(root)
C. 删除操作
def deleteNode(root, key):
if not root:
return None
if key < root.val:
root.left = deleteNode(root.left, key)
elif key > root.val:
root.right = deleteNode(root.right, key)
else:
if not root.left:
return root.right
if not root.right:
return root.left
# 找到右子树最小节点,将被删除节点的左节点作为该右子树最小节点的左子节点
node = root.right
while node.left:
node = node.left
root.val = node.val
root.right = deleteNode(root.right, node.val)
return root
删除操作相对而言更加复杂,删除一个节点后,为了保持二叉搜索树的性质,需要考虑三种情况: 只有左子树/只有右子树/左右子树都存在
D. 修建BST
def trimBST(root, low, high):
if not root:
return None
if root.val < low:
return trimBST(root.right, low, high)
if root.val > high:
return trimBST(root.left, low, high)
root.left = trimBST(root.left, low, high)
root.right = trimBST(root.right, low, high)
return root
由于二叉搜索树的性质,直接根据区间 [low, high]
进行剪枝即可,递归处理左右子树。需要注意的是,若当前根节点小于给定区间的最小值,或大于给定区间的最大值时,其子树仍然可能满足条件,需要继续向相应子树方向递归。
E. 最近公共祖先
def lowestCommonAncestor(root, p, q):
if not root:
return None
if p.val < root.val and q.val < root.val:
return lowestCommonAncestor(root.left, p, q)
if p.val > root.val and q.val > root.val:
return lowestCommonAncestor(root.right, p, q)
return root
当一个节点的值首次在区间 [p.val,q.val]
内时,该节点即为两个 p, q 的最近公共祖先
F. 二叉搜索树中节点的最小绝对差
def getMinimumDifference(root: Optional[TreeNode]) -> int:
from collections import deque
myQueue = deque()
myQueue.append([root, False])
pre = None
minDiff = float('inf')
while myQueue:
node, visited = myQueue.pop()
if visited:
cur = node
if pre is None:
pass
elif cur.val - pre.val < minDiff:
minDiff = cur.val - pre.val
pre = cur
continue
if node.right:
myQueue.append([node.right, False])
myQueue.append([node, True])
if node.left:
myQueue.append([node.left, False])
return minDiff
利用 BST 的 中序有序性,相邻节点差值最小。
此处给出的实现参考运用迭代法,在中序遍历的过程中,即实时更新节点之间的最小绝对差。在具体实现时,为了方便理解,也可以使用递归法先获取二叉搜索树的中序遍历序列,然后再逐一比较两个元素之间的最小绝对差
G. BST转换为累加树
def convertBST(root: Optional[TreeNode]) -> Optional[TreeNode]:
def traverse(cur: Optional[TreeNode], sum: int) -> int:
if cur is None:
return sum
right_sum = traverse(cur.right, sum)
val = cur.val + right_sum
cur.val = val
return traverse(cur.left, val)
traverse(root, 0)
return root
倒序中序遍历(右 → 根 → 左),累加更新。
- 小结
- 搜索 / 插入 / 删除:体现了 BST 的结构特性。
- 修剪 / LCA:利用 BST 有序性,省去不必要的递归。
- 最小差值 / 众数:中序遍历特性,保证有序性。
- 转化为累加树:倒序中序 → 累加和。
一遇到BST问题,第一反应是中序遍历有序性
六,完全二叉树(待后续补充)
- 思路归纳
- 完全二叉树特性: 叶子节点只能出现在最下层和次下层,且最下层的叶子节点集中在树的最左边
- 完全二叉树的节点数量: 完全二叉树中包含了满二叉树子树,利用满二叉树子树快速计算节点的个数。
- 题单
完全二叉树节点个数 222
- 核心代码实现
A. 完全二叉树节点个数
def countNodes(root: Optional[TreeNode]) -> int:
if root is None:
return 0
leftDepth, rightDepth = 1, 1
leftNode, rightNode = cur.left, cur.right
while leftNode:
leftDepth += 1
leftNode = leftNode.left
while rightNode:
rightDepth += 1
rightNode = rightNode.right
# 以当前节点为根节点的二叉树为满二叉树,因为左右叶子深度相同,直接返回其节点数量
if leftDepth == rightDepth:
return 2 ** rightDepth - 1
# 返回左右子树节点数量之和
return 1 + getCount(root.left) + getCount(root.right)
七,总结
二叉树思路整体归纳
- 三大解题套路
-
自底向上(Bottom-up)
- 典型应用:子树属性问题(深度、高度、平衡、LCA 普通树)
- 关键:递归返回子树信息,逐层汇总
-
自顶向下(Top-down)
- 典型应用:路径问题、带约束的统计(路径和、左叶子之和)
- 关键:递归传递参数 → 上层控制下层遍历
-
回溯(Backtracking / DFS)
- 典型应用:方案枚举问题(路径枚举、子集、排列问题)
- 关键:递归 + 状态回退
- BST 与普通二叉树的区别
- BST 核心性质:中序遍历严格递增
- 利用有序性可以 剪枝 / 快速定位 / 优化遍历
- 普通二叉树不具备有序性 → 必须全局搜索或自底向上汇总信息
类型 | 典型题 | 推荐套路 |
---|---|---|
子树属性问题 | 深度、高度、平衡、对称 | Bottom-up |
路径 / 目标问题 | 路径和、左叶子之和、左下角值 | Top-down |
方案枚举 | 路径枚举、排列组合 | DFS + 回溯 |
BST 问题 | 搜索 / 插入 / 删除 / LCA / 修剪 | 利用中序有序性 |