🌳 二叉树 — 递归的完美舞台

二叉树是递归思维的训练场,掌握遍历就掌握了树的灵魂

📖 二叉树的核心概念

二叉树(Binary Tree)是每个节点最多有两个子节点的树结构。二叉树是递归定义的——一棵二叉树要么为空,要么由根节点、左子树和右子树组成。这个递归定义使得二叉树的几乎所有问题都可以用递归优雅地解决。

二叉树的结构与遍历: 3 / \ 9 20 / \ 15 7 三种递归遍历: 前序(根左右): 3 → 9 → 20 → 15 → 7 中序(左根右): 9 → 3 → 15 → 20 → 7 后序(左右根): 9 → 15 → 7 → 20 → 3 层序遍历(BFS): 第0层: [3] 第1层: [9, 20] 第2层: [15, 7] 特殊二叉树: ┌──────────┬────────────────────────────┐ │ 满二叉树 │ 每个节点有0或2个子节点 │ │ 完全二叉树│ 最后一层从左到右填满 │ │ 二叉搜索树│ 左<根<右(中序遍历有序) │ │ 平衡二叉树│ 左右子树高度差≤1 │ └──────────┴────────────────────────────┘

1. 二叉树递归思维框架

自顶向下(前序位置):先处理当前节点,再递归子树。
适用:需要传递父节点信息给子节点的问题。
示例:求最大深度(传深度参数下去)。
自底向上(后序位置):先递归子树,再根据子树结果处理当前节点。
适用:需要子树信息才能计算当前节点的问题。
示例:求最大深度(子树深度+1)、判断平衡。

2. LeetCode 二叉树节点定义

# 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
二叉树问题的万能模板:想清楚三件事——(1) 函数返回什么?(2) 递归基是什么?(3) 当前节点如何利用左右子树的结果?

🎯 题目一:二叉树的最大深度 (LC 104)

题目描述:给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

示例: 3 / \ 9 20 / \ 15 7 最大深度 = 3 自底向上递归: depth(9) = 1 (叶子) depth(15) = 1 (叶子) depth(7) = 1 (叶子) depth(20) = max(1, 1) + 1 = 2 depth(3) = max(1, 2) + 1 = 3 ← 答案!

思路分析

自底向上递归:
递归计算左右子树的最大深度,当前节点深度 = max(左深度, 右深度) + 1。
递归基:空节点深度为 0。
时间复杂度:O(n),空间复杂度:O(h),h 为树高

代码实现

class Solution:
    def maxDepth(self, root: Optional[TreeNode]) -> int:
        if not root:
            return 0
        return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1

复杂度分析

时间复杂度:O(n) — 访问每个节点一次

空间复杂度:O(h) — 递归栈深度

变体练习

🎯 题目二:翻转二叉树 (LC 226)

题目描述:给你一棵二叉树的根节点 root,翻转这棵二叉树,并返回其根节点。

示例: 4 4 / \ / \ 2 7 →→→ 7 2 / \ / \ / \ / \ 1 3 6 9 9 6 3 1 递归过程: flip(4): swap(left=2, right=7) → flip(2), flip(7) flip(2): swap(left=1, right=3) → flip(1), flip(3) flip(1): 左右都空 → 返回 flip(3): 左右都空 → 返回 flip(7): swap(left=6, right=9) → flip(6), flip(9) ... 核心操作: 交换每个节点的左右子树

思路分析

递归交换:
对每个节点,交换其左右子树,然后递归翻转左右子树。
可以在前序位置交换(先交换再递归),也可以在后序位置(先递归再交换)。
时间复杂度:O(n),空间复杂度:O(h)

代码实现

class Solution:
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        if not root:
            return None
        root.left, root.right = root.right, root.left
        self.invertTree(root.left)
        self.invertTree(root.right)
        return root

复杂度分析

时间复杂度:O(n)

空间复杂度:O(h)

这题的梗:"Homebrew作者不会翻转二叉树被Google拒了"。看似简单,但体现了递归的优雅——3行代码解决!

变体练习

🎯 题目三:二叉树的层序遍历 (LC 102)

题目描述:给你二叉树的根节点 root,返回其节点值的层序遍历(即逐层地,从左到右访问所有节点)。

示例: 3 / \ 9 20 / \ 15 7 层序遍历: [[3], [9, 20], [15, 7]] BFS过程: 队列: [3] → 弹出3, 加入[9,20] → 第0层: [3] 队列: [9, 20] → 弹出9(无子), 弹出20 → 第1层: [9,20] 队列: [15, 7] → 弹出15, 弹出7 → 第2层: [15,7] 关键: 每轮处理一整层, 记录层大小

思路分析

BFS 层序遍历:
1. 队列初始化放入根节点
2. 每轮记录当前层大小 size,弹出 size 个节点,收集值
3. 每个弹出节点的左右子节点入队
4. 直到队列为空
时间复杂度:O(n),空间复杂度:O(w),w 为最大宽度

代码实现

class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        from collections import deque
        if not root:
            return []
        result = []
        queue = deque([root])
        while queue:
            level = []
            for _ in range(len(queue)):
                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

复杂度分析

时间复杂度:O(n)

空间复杂度:O(w) — w 为树的最大宽度

层序遍历的关键:每轮开始时记录 len(queue) 作为当前层大小,否则队列长度会因新元素入队而变化,导致层级混乱。

变体练习

成就解锁:树之语者 — 掌握二叉树递归三件套:最大深度、翻转、层序遍历
LeetCode AC验证:LC 104 Maximum Depth of Binary Tree ✅ | LC 226 Invert Binary Tree ✅ | LC 102 Binary Tree Level Order Traversal ✅

📝 课后练习

  1. LC 101 对称二叉树(递归判断左右镜像)
  2. LC 111 二叉树的最小深度
  3. LC 110 平衡二叉树(后序递归判断)
  4. LC 257 二叉树的所有路径(前序遍历+回溯)
  5. LC 103 二叉树的锯齿形层序遍历

🔑 本课要点回顾

📚 扩展阅读

思考题:如何判断一棵二叉树是否是二叉搜索树(BST)?提示:中序遍历有序,或递归时传递上下界。

🔬 二叉树的序列化与反序列化

二叉树的序列化是将树转化为字符串,反序列化是还原。这是面试中的经典设计题,也是对递归思维的深度考验。

序列化示例: 1 / \ 2 3 / \ 4 5 前序序列化: "1,2,null,null,3,4,null,null,5,null,null" 读取过程(反序列化): 读1 → 创建节点(1), 递归左子树 读2 → 创建节点(2), 递归左子树 读null → 返回None 递归右子树 读null → 返回None 递归右子树 读3 → 创建节点(3), 递归左子树 读4 → ... 递归右子树 读5 → ... 关键: null标记空节点, 前序遍历的递归结构 天然适合反序列化!

代码实现

class Codec:
    def serialize(self, root):
        if not root:
            return 'null'
        return f'{root.val},{self.serialize(root.left)},{self.serialize(root.right)}'
    
    def deserialize(self, data):
        vals = iter(data.split(','))
        def build():
            val = next(vals)
            if val == 'null':
                return None
            node = TreeNode(int(val))
            node.left = build()
            node.right = build()
            return node
        return build()
层序遍历也可以序列化,但反序列化时需要用队列 BFS 还原,代码稍复杂。前序 + 递归是最优雅的方案。
思考题:如何序列化一棵二叉搜索树使得序列化字符串最短?提示:BST 只需存前序或后序遍历即可还原,不需要存 null——因为可以根据值的范围确定左右子树的分界点。