30 秒速览
- 什么时候用:树形结构、递归问题、层次关系
- 核心思维:递归三要素——明确函数定义、确定终止条件、写单层逻辑
- 遍历方式:前序(根左右)、中序(左根右)、后序(左右根)、层序
- 时间复杂度:通常 O(n)
一、直觉建立
什么是二叉树?
想象一棵倒过来的树,最上面是树根,往下分叉。每个节点最多分出两个叉(左边一个,右边一个)。
生活中的二叉树:
- 公司组织架构:CEO → 两个副总裁 → 部门经理
- 文件夹结构:层层嵌套
- 家谱树:每个人最多有两个父母
为什么递归在树中如此自然?
核心洞察:树本身就是递归定义的!
一棵树 = 根节点 + 左子树(也是树) + 右子树(也是树)
这就像俄罗斯套娃,大娃娃里套着小娃娃。所以处理树的问题:
- 处理根节点
- 递归处理左子树(相信它能处理好)
- 递归处理右子树(相信它能处理好)
“相信递归”:就像委派任务给下属,你不需要盯着他每一步,只需要告诉他要做什么,相信他能做好。
二、二叉树遍历
前中后序遍历
核心区别:根节点的处理时机
def traverse(root):
if not root:
return
# 前序:根 → 左 → 右
print(root.val) # 在这里处理根(第一时间)
traverse(root.left)
traverse(root.right)
# 中序:左 → 根 → 右
traverse(root.left)
print(root.val) # 在这里处理根(中间)
traverse(root.right)
# 后序:左 → 右 → 根
traverse(root.left)
traverse(root.right)
print(root.val) # 在这里处理根(最后)
遍历的应用:
| 遍历方式 | 应用场景 |
|---|---|
| 前序 | 复制树、打印目录结构 |
| 中序 | BST 得到有序序列 |
| 后序 | 删除树、计算目录大小 |
递归实现
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
# 前序遍历
def preorderTraversal(root: TreeNode) -> list[int]:
result = []
def traverse(node):
if not node:
return
result.append(node.val)
traverse(node.left)
traverse(node.right)
traverse(root)
return result
# 中序遍历
def inorderTraversal(root: TreeNode) -> list[int]:
result = []
def traverse(node):
if not node:
return
traverse(node.left)
result.append(node.val)
traverse(node.right)
traverse(root)
return result
# 后序遍历
def postorderTraversal(root: TreeNode) -> list[int]:
result = []
def traverse(node):
if not node:
return
traverse(node.left)
traverse(node.right)
result.append(node.val)
traverse(root)
return result
层序遍历(BFS)
from collections import deque
def levelOrder(root: TreeNode) -> list[list[int]]:
if not root:
return []
result = []
queue = deque([root])
while queue:
level_size = len(queue)
level = []
for _ in range(level_size):
node = queue.popleft()
level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(level)
return result
三、典型例题
例题 1:LC 104. 二叉树的最大深度
题目:给定一个二叉树,找出其最大深度。
递归思路(后序遍历)
树的深度 = max(左子树深度, 右子树深度) + 1
def maxDepth(root: TreeNode) -> int:
if not root:
return 0
left_depth = maxDepth(root.left)
right_depth = maxDepth(root.right)
return max(left_depth, right_depth) + 1
复杂度:时间 O(n),空间 O(h),h 是树高
例题 2:LC 226. 翻转二叉树
题目:翻转一棵二叉树。
递归思路
交换左右子树,递归处理。
def invertTree(root: TreeNode) -> TreeNode:
if not root:
return None
# 交换左右子树
root.left, root.right = root.right, root.left
# 递归处理
invertTree(root.left)
invertTree(root.right)
return root
复杂度:时间 O(n),空间 O(h)
例题 3:LC 101. 对称二叉树
题目:给定一个二叉树,检查它是否是镜像对称的。
思路
比较左子树和右子树是否互为镜像。
def isSymmetric(root: TreeNode) -> bool:
def isMirror(left, right):
if not left and not right:
return True
if not left or not right:
return False
return (left.val == right.val and
isMirror(left.left, right.right) and
isMirror(left.right, right.left))
return isMirror(root.left, root.right) if root else True
复杂度:时间 O(n),空间 O(h)
例题 4:LC 543. 二叉树的直径
题目:给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。
思路
直径 = 左子树最大深度 + 右子树最大深度(不一定经过根节点)
def diameterOfBinaryTree(root: TreeNode) -> int:
max_diameter = 0
def depth(node):
nonlocal max_diameter
if not node:
return 0
left = depth(node.left)
right = depth(node.right)
# 更新最大直径
max_diameter = max(max_diameter, left + right)
return max(left, right) + 1
depth(root)
return max_diameter
复杂度:时间 O(n),空间 O(h)
例题 5:LC 236. 二叉树的最近公共祖先
题目:给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
思路
后序遍历,找到 p 或 q 就往上传递。
def lowestCommonAncestor(root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
if not root or root == p or root == q:
return root
left = lowestCommonAncestor(root.left, p, q)
right = lowestCommonAncestor(root.right, p, q)
if left and right:
return root # p 和 q 分别在左右子树
return left if left else right # 都在同一侧
复杂度:时间 O(n),空间 O(h)
例题 6:LC 124. 二叉树中的最大路径和(Hard)
题目:路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。
思路
对于每个节点,最大路径可能是:
- 只经过左子树的一条路径
- 只经过右子树的一条路径
- 经过当前节点,连接左右子树
def maxPathSum(root: TreeNode) -> int:
max_sum = float('-inf')
def maxGain(node):
nonlocal max_sum
if not node:
return 0
# 递归计算左右子树的最大贡献
left_gain = max(maxGain(node.left), 0)
right_gain = max(maxGain(node.right), 0)
# 经过当前节点的最大路径和
current_path = node.val + left_gain + right_gain
max_sum = max(max_sum, current_path)
# 返回当前节点能向上贡献的最大值
return node.val + max(left_gain, right_gain)
maxGain(root)
return max_sum
复杂度:时间 O(n),空间 O(h)
例题 7:LC 98. 验证二叉搜索树
题目:给定一个二叉树,判断其是否是一个有效的二叉搜索树。
思路一:中序遍历
BST 中序遍历是递增的。
def isValidBST(root: TreeNode) -> bool:
prev = float('-inf')
def inorder(node):
nonlocal prev
if not node:
return True
if not inorder(node.left):
return False
if node.val <= prev:
return False
prev = node.val
return inorder(node.right)
return inorder(root)
思路二:递归验证范围
def isValidBST(root: TreeNode) -> bool:
def validate(node, low, high):
if not node:
return True
if node.val <= low or node.val >= high:
return False
return (validate(node.left, low, node.val) and
validate(node.right, node.val, high))
return validate(root, float('-inf'), float('inf'))
复杂度:时间 O(n),空间 O(h)
例题 8:LC 450. 删除二叉搜索树中的节点
题目:给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。
思路
- 找到要删除的节点
- 如果节点是叶子,直接删除
- 如果节点有一个子节点,用子节点替换
- 如果节点有两个子节点,用后继节点(右子树最小值)替换
def deleteNode(root: TreeNode, key: int) -> TreeNode:
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
# 两个子节点都存在,找后继节点
successor = root.right
while successor.left:
successor = successor.left
root.val = successor.val
root.right = deleteNode(root.right, successor.val)
return root
复杂度:时间 O(h),空间 O(h)
四、递归三要素
要素 1:明确函数定义
def maxDepth(root):
"""返回以 root 为根的树的最大深度"""
要素 2:确定终止条件
if not root:
return 0 # 空树深度为 0
要素 3:写单层递归逻辑
left = maxDepth(root.left) # 相信左子树能算出深度
right = maxDepth(root.right) # 相信右子树能算出深度
return max(left, right) + 1 # 当前层的逻辑
五、复盘清单
触发信号
- 树形结构
- 层次关系
- 需要遍历所有节点
- 需要自底向上/自顶向下处理
边界条件
- 空树
- 单节点树
- 只有左子树/右子树
- 完全不平衡的树(退化为链表)
常见错误
- 忘记终止条件(无限递归)
- 没有处理空节点
- 递归返回值使用错误
六、题目清单
基础题
| 题号 | 题目 | 难度 | 考点 |
|---|---|---|---|
| 104 | 二叉树的最大深度 | Easy | 后序遍历 |
| 226 | 翻转二叉树 | Easy | 前序遍历 |
| 101 | 对称二叉树 | Easy | 递归比较 |
| 102 | 二叉树的层序遍历 | Medium | BFS |
| 257 | 二叉树的所有路径 | Easy | 前序遍历 |
进阶题
| 题号 | 题目 | 难度 | 考点 |
|---|---|---|---|
| 236 | 最近公共祖先 | Medium | 后序遍历 |
| 543 | 二叉树的直径 | Easy | 深度计算 |
| 124 | 二叉树最大路径和 | Hard | 后序遍历 |
| 98 | 验证BST | Medium | 中序遍历/范围验证 |
| 450 | 删除BST节点 | Medium | BST 操作 |
| 114 | 二叉树展开为链表 | Medium | 前序遍历 |