🌳 二叉树 — 递归的完美舞台
二叉树是递归思维的训练场,掌握遍历就掌握了树的灵魂
📖 二叉树的核心概念
二叉树(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 111 二叉树的最小深度
- LC 559 N 叉树的最大深度
🎯 题目二:翻转二叉树 (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 101 对称二叉树(判断是否镜像对称)
- LC 100 相同的树
🎯 题目三:二叉树的层序遍历 (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) 作为当前层大小,否则队列长度会因新元素入队而变化,导致层级混乱。
变体练习
- LC 103 二叉树的锯齿形层序遍历(之字形)
- LC 107 二叉树的层序遍历 II(自底向上)
- LC 637 二叉树的层平均值
成就解锁:树之语者 — 掌握二叉树递归三件套:最大深度、翻转、层序遍历
LeetCode AC验证:LC 104 Maximum Depth of Binary Tree ✅ | LC 226 Invert Binary Tree ✅ | LC 102 Binary Tree Level Order Traversal ✅
📝 课后练习
- LC 101 对称二叉树(递归判断左右镜像)
- LC 111 二叉树的最小深度
- LC 110 平衡二叉树(后序递归判断)
- LC 257 二叉树的所有路径(前序遍历+回溯)
- LC 103 二叉树的锯齿形层序遍历
🔑 本课要点回顾
- 二叉树递归三要素:返回值、递归基、利用左右子树结果
- 自顶向下(前序):先处理当前,再递归子树
- 自底向上(后序):先递归子树,再利用结果
- 最大深度:max(左, 右) + 1,后序递归
- 翻转:交换左右子树,前序或后序都可以
- 层序遍历:BFS + 记录每层大小,区分层级
思考题:如何判断一棵二叉树是否是二叉搜索树(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——因为可以根据值的范围确定左右子树的分界点。