数据结构与算法
算法二叉树学习路线
二叉树专题导读
为什么二叉树是算法的分水岭
链表是线性递推,数组是线性下标,二叉树第一次让"数据结构"出现了分叉:一个节点有两个后继,问题天然裂解成"左子树怎么办、右子树怎么办"。这个裂解恰恰是递归思维的完美训练场——绝大多数二叉树题的本质,是把一个全局问题拆成两个形状相同但规模更小的子问题。
树也是后续所有章节的地基:回溯的决策树、二叉搜索树的查找、堆的完全二叉树实现、甚至红黑树和 B+ 树(数据库索引的底层),全都建立在二叉树的概念之上。这一章慢一点、透一点,后面全是顺风局。
四种特殊形态的定义要抠准
二叉树本身定义简单:每个节点至多两个孩子的树。真正容易混淆的是四个"形容词",定义边界必须一字不差:
| 种类 | 定义 | 关键边界 | 现实应用 |
|---|---|---|---|
| 满二叉树 | 每层节点都达到最大,深度 k 有 2^k − 1 个节点 | 一层都不能少,总数可由深度直接算出 | 理论模型 |
| 完全二叉树 | 除最底层外全满,最底层节点从左到右连续 | 右边可以缺,左边不能断 | 堆的底层结构 |
| 二叉搜索树 | 任意节点:左子树所有节点 < 根 < 右子树所有节点 | 约束的是整个子树,不只父子 | 查找、有序集合 |
| 平衡二叉树 | 任意节点左右子树高度差 ≤ 1 | 约束的是每个节点都平衡 | AVL/红黑树的基础 |
两个最常踩的坑:
- 完全二叉树的判断看"编号连续性":按层编号,若中间出现空洞就不是。
[1,2,3,_,5]不是完全二叉树(4 号缺但 5 号在),[1,2,3,4,_]是。 - 二叉搜索树是"整棵左子树都小于根",不是"左孩子小于根"。
[5,1,6,3,7]若只比父子会误判为 BST——3 在 6 的左子树里却在 5 的右子树一侧,违反全局约束。验证 BST 必须传上下界或用中序有序性。
Java 节点定义与两种存储
刷题用的标准节点定义(LeetCode 已内置,自己本地调试时需要手写):
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}树在内存里有两种存法:
链式存储:就是上面的节点定义,用引用串起来。刷题 99% 用它,插入删除灵活,缺点是只有指针没有下标,无法 O(1) 定位"第 i 个节点"。
数组存储(堆的存储方式):按层序编号放进数组,编号 i 的节点:
- 左孩子:
2i + 1,右孩子:2i + 2(0 起始下标); - 父节点:
(i - 1) / 2。
A(0)
/ \
B(1) C(2)
/ \
D(3) E(4)
数组: [A, B, C, D, E]
下标: 0 1 2 3 4 E 的下标 4 → 左孩子 9、右孩子 10,越界即无孩子
数组存储省掉所有指针开销,但要求数组形状尽量"满"——这正是完全二叉树的主场,PriorityQueue(堆)和 Arrays.sort 的底层都基于它。理解 2i+1 / 2i+2,堆的上浮下沉代码就不再神秘。
专题学习地图
二叉树题目数量庞大,但没有必要恐惧——按下面的主线走,每一步都是在前一步上加一点点东西:
二叉树
├── 第一层:遍历(一切的地基)
│ ├── 前中后序递归 / 迭代 / 统一迭代
│ └── 层序遍历 BFS(102 系)
├── 第二层:属性求值(遍历 + 返回值设计)
│ ├── 深度与高度:104 / 111 / 110
│ ├── 结构判断:101 对称 / 222 节点数
│ └── 路径类:112 路径总和 / 257 所有路径
├── 第三层:修改与构造
│ ├── 226 翻转 / 617 合并
│ ├── 105 前序+中序构造 / 106 中序+后序构造
│ └── 654 最大二叉树 / 108 有序数组转 BST
├── 第四层:二叉搜索树
│ ├── 700 搜索 / 98 验证 / 530 最小绝对差
│ ├── 701 插入 / 450 删除 / 669 修剪
│ └── 538 累加树(中序逆序)
└── 第五层:公共祖先
├── 236 普通二叉树的最近公共祖先
└── 235 BST 的最近公共祖先(利用有序性剪枝)
- 第一层是地基:不会遍历,后面全部免谈。递归三要素 + 层序 BFS 模板要练到肌肉记忆。
- 第二层学"返回值设计":求高度用后序(自底向上收集),求深度用前序(自顶向下传递),这是本层核心功法。
- 第三层学"分而治之":构造类题目的套路高度一致——找根、切分左右区间、递归构造。
- 第四层吃透"有序性":BST 的中序遍历天然有序,一半的 BST 题靠这一条性质解决。
- 第五层是综合:公共祖先的递归后序逻辑,是前四层所有技巧的集中演练。
学习方法建议
- 先写递归三要素再动手:函数参数和返回值是什么、终止条件是什么、单层逻辑是什么。三句话说不出,代码写出来也是碰运气。
- 树形结构一定要画图:递归调用栈在纸上画展开图,比在脑子里空转有效十倍。
- 一个函数一个职责:求高度就只求高度,不要顺便做平衡判断,职责混在一起往往写出 O(n²) 的隐藏 bug。
LeetCode 大题单(分块)
| 板块 | 题号 | 题名 | 一句话考点 |
|---|---|---|---|
| 遍历 | 144/94/145 | 前序/中序/后序遍历 | 递归三行 vs 迭代栈 vs 统一迭代 |
| 遍历 | 102 | 层序遍历 | BFS 模板,按层收集 |
| 遍历 | 107/199/637/429 | 层序衍生 | 只改每层的收集逻辑 |
| 属性 | 101 | 对称二叉树 | 外侧/内侧双指针递归 |
| 属性 | 104/111 | 最大/最小深度 | 深度前序、高度后序;111 有单侧为空的坑 |
| 属性 | 110 | 平衡二叉树 | 求高度返回 -1 剪枝 |
| 属性 | 112 | 路径总和 | 递减目标值到叶子判断 |
| 修改构造 | 226/617 | 翻转/合并 | 最朴素的"处理左右 + 返回根" |
| 修改构造 | 105/106 | 遍历序列构造 | 找根→切区间→递归,HashMap 缓存下标 |
| BST | 700/98 | 搜索/验证 | 利用有序性;98 必须传上下界 |
| BST | 701/450 | 插入/删除 | 删除节点五种情况讨论 |
| BST | 530/501/538 | 中序性质 | 有序序列上做差值/众数/累加 |
| 祖先 | 236/235 | 最近公共祖先 | 后序收集左右结果;235 借有序性直走 |
题单虽长,但每个板块只引入一个新概念。按板块顺序推进,每个板块内先做加粗的经典题再做衍生题,两三周可以完整走一遍。