05 · 二叉树

来源: labuladong 遍历法 vs 分解法 + 代码随想录递归三步曲 + 灵茶山艾府 B 站讲解
核心价值: 二叉树是理解递归的最佳载体,tree 型递归是所有递归的基础
题量: 15 题(Hot 100 第二大数据结构)


一、本质理解

二叉树 = 链表的分叉版。 labuladong 将二叉树归类为”链表的延伸”——每个节点有两个 next 指针(left 和 right)。

更深层的理解:二叉树是理解递归的入口。 几乎所有二叉树问题都可以用递归解决,并且递归的模式非常固定。

labuladong 两大流派

二叉树解题
├── 遍历法:遍历整棵树,过程中更新全局变量
│   → 类似"打擂台",适用于统计类、查找类问题
│   → 前序、中序、后序决定处理时机
│
└── 分解法:定义递归返回值,用子问题结果构造当前答案
    → 类似"分治",适用于子树属性类、构造类问题
    → 关键是明确递归函数的"含义"

代码随想录递归三步曲

第一步:确定递归函数的参数和返回值
第二步:确定终止条件(base case)
第三步:确定单层递归逻辑(前/中/后序操作)

二、遍历框架

DFS(深度优先)

def traverse(root):
    if not root:  # base case
        return
    
    # 前序位置(刚进入节点时)
    traverse(root.left)
    # 中序位置(左子树返回后)
    traverse(root.right)
    # 后序位置(右子树返回后)

三个位置的关键区别:

位置时机典型应用
前序刚进入节点,还没处理子树构造、复制、翻转
中序左子树处理完,右子树还没处理BST 相关(有序)
后序左右子树都处理完求深度、路径、直径

BFS(层序遍历)

from collections import deque
 
def level_order(root):
    if not root:
        return []
    res = []
    q = deque([root])
    
    while q:
        level_size = len(q)
        level = []
        for _ in range(level_size):
            node = q.popleft()
            level.append(node.val)
            if node.left:
                q.append(node.left)
            if node.right:
                q.append(node.right)
        res.append(level)
    
    return res

三、核心模板

模板 1:二叉树最大深度(后序 + 分解法)

def max_depth(root):
    if not root:
        return 0
    left_depth = max_depth(root.left)
    right_depth = max_depth(root.right)
    return 1 + max(left_depth, right_depth)

模板 2:翻转二叉树(前序 + 遍历法)

def invert_tree(root):
    if not root:
        return None
    
    # 前序位置:交换左右子树
    root.left, root.right = root.right, root.left
    
    invert_tree(root.left)
    invert_tree(root.right)
    
    return root

模板 3:验证 BST(中序 + 全局变量)

def is_valid_bst(root):
    # 中序遍历 BST 应严格递增
    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)

模板 4:最近公共祖先(后序 + 分解法)

def lowest_common_ancestor(root, p, q):
    if not root or root == p or root == q:
        return root
    
    left = lowest_common_ancestor(root.left, p, q)
    right = lowest_common_ancestor(root.right, p, q)
    
    if left and right:  # p 和 q 分别在左右子树
        return root
    return left or right  # 返回找到的那一侧

模板 5:从前序+中序构造二叉树

def build_tree(preorder, inorder):
    # 用哈希表加速查找根节点在中序中的位置
    index_map = {val: i for i, val in enumerate(inorder)}
    
    def build(pre_l, pre_r, in_l, in_r):
        if pre_l > pre_r:
            return None
        
        root_val = preorder[pre_l]
        root = TreeNode(root_val)
        in_idx = index_map[root_val]
        left_size = in_idx - in_l
        
        root.left = build(pre_l + 1, pre_l + left_size, in_l, in_idx - 1)
        root.right = build(pre_l + left_size + 1, pre_r, in_idx + 1, in_r)
        
        return root
    
    return build(0, len(preorder) - 1, 0, len(inorder) - 1)

四、BST 性质总结

性质说明用途
中序有序BST 的中序遍历是严格递增序列验证 BST、找第 k 小
左 < 根 < 右所有左子树节点 < 根 < 所有右子树节点递归定义
搜索比较 target 和 root.val 决定方向O(log n) 搜索

五、Hot 100 二叉树题目清单

Day 6:二叉树基础

题号题目难度核心技巧建议用时
94中序遍历Easy递归/迭代栈20 min
104最大深度Easy后序递归20 min
226翻转二叉树Easy前序交换20 min
101对称二叉树Easy递归比较25 min
543二叉树直径Easy后序求深度25 min
102层序遍历MediumBFS 队列25 min
108有序数组转 BSTEasy递归构建25 min
98验证 BSTMedium中序有序性30 min

Day 7:二叉树进阶

题号题目难度核心技巧建议用时
230BST 第 k 小Medium中序+计数器25 min
199右视图MediumBFS 每层最右25 min
114展开为链表Medium后序+prev 指针30 min
105从前中序构造Medium递归+哈希表35 min
437路径总和 IIIMedium前缀和+DFS35 min
236最近公共祖先Medium后序递归30 min
124最大路径和Hard后序+全局最大35 min

六、易错点与技巧

  1. 递归的返回值 vs 全局变量:需要子树信息时用返回值(后序),需要路径信息时用全局变量(前序+回溯)
  2. None 的处理:始终先判断 if not root: return
  3. BST 的中序有序性:验证 BST 用中序,不要只比较当前节点和左右子节点
  4. 路径和问题:437 题用前缀和思路,类似数组中的”和为 K 的子数组”
  5. 前序+中序构造:关键在于找到根节点在中序中的位置,从而确定左右子树大小

七、复杂度总结

操作时间复杂度空间复杂度
遍历(递归)O(n)O(h),h 为树高
遍历(迭代)O(n)O(n)
构造二叉树O(n)O(n)
BST 搜索O(log n)~O(n)O(h)

八、参考与延伸