“树”立信心,“码”到成功!
—— 本文带你用轻松幽默的方式掌握 LeetCode 二叉树高频题型,附带 JS 实现,助你面试不再怕树!
🧠 为什么 LeetCode 总爱考「二叉树」?
因为——它结构清晰、递归友好、套路明显,简直是算法题界的“模范生”!
而且,只要你掌握了那几招核心思想(前中后序遍历、DFS/BFS、分治、BST 性质),80% 的二叉树题都能迎刃而解。
今天,我们就以 28 道经典 LeetCode 二叉树题目为线索,一起打通任督二脉,顺便笑出腹肌 😎
🌱 第一式:遍历三剑客 —— 前序、中序、后序
口诀:前中后 = 根左右 / 左根右 / 左右根
这三位是所有二叉树操作的基石。记住:递归写法简洁如诗,迭代写法锻炼栈功底。
// 前序(根左右)
var preorderTraversal = function(root) {
let res = [];
const dfs = (node) => {
if (!node) return;
res.push(node.val);
dfs(node.left);
dfs(node.right);
};
dfs(root);
return res;
}
✅ 小贴士:前序适合“复制树”或“路径记录”;中序在 BST 中能输出有序数组;后序常用于“删除”或“计算子树和”。
🔁 第二式:层序遍历 —— BFS 的主场
队列出场,逐层收割!
var levelOrder = function(root) {
if (!root) return [];
let queue = [root], res = [];
while (queue.length) {
let len = queue.length, level = [];
for (let i = 0; i < len; i++) {
let node = queue.shift();
level.push(node.val);
node.left && queue.push(node.left);
node.right && queue.push(node.right);
}
res.push(level);
}
return res;
}
💡 应用场景:找树的左下角值(513)、最大宽度、按层打印……甚至可以用来判断完全二叉树!
🔄 第三式:翻转 & 合并 —— 递归的魔法
翻转二叉树(226)—— Google 面试题!
var invertTree = function(root) {
if (!root) return null;
[root.left, root.right] = [invertTree(root.right), invertTree(root.left)];
return root;
}
🤯 梗来了:有人问 Max Howell(Homebrew 作者):“你能写出翻转二叉树吗?”
他答:“我写了 Homebrew,但不会翻转二叉树。”
结果被 Google 拒了……所以,这题你必须会!
合并二叉树(617)
两棵树叠在一起?重叠就相加,不重叠就继承!
var mergeTrees = function(r1, r2) {
if (!r1) return r2;
if (!r2) return r1;
r1.val += r2.val;
r1.left = mergeTrees(r1.left, r2.left);
r1.right = mergeTrees(r1.right, r2.right);
return r1;
}
❤️ 浪漫解读:这不是合并树,这是“爱情树”——你中有我,我中有你!
🌲 第四式:二叉搜索树(BST)专属技能包
BST 是有纪律的树:左 < 根 < 右。利用这个性质,很多操作可以 O(log n) 完成!
1. 搜索(700)—— 直接二分!
if (root.val === val) return root;
return val < root.val ? searchBST(root.left, val) : searchBST(root.right, val);
2. 插入(701)—— 找到空位就坐下!
if (!root) return new TreeNode(val);
if (val < root.val) root.left = insertIntoBST(root.left, val);
else root.right = insertIntoBST(root.right, val);
return root;
3. 删除(450)—— 最难搞的“善后工作”
- 没孩子?直接删。
- 一个孩子?孩子顶上。
- 两个孩子?找右子树最小值(或左子树最大值)来接班!
// 两个孩子的情况
let cur = root.right;
while (cur.left) cur = cur.left; // 找右子树的最左节点
cur.left = root.left;
return root.right;
⚠️ 注意:别忘了递归更新父节点的指针!
4. 验证 BST(98)—— 中序遍历看是否升序!
buildArr(root); // 中序收集值
for (let i = 1; i < arr.length; i++) {
if (arr[i] <= arr[i-1]) return false;
}
❗ 别踩坑:不能只比较 root 和左右孩子!要全局有序!
🎯 第五式:路径 & 深度 & 平衡 —— DFS 的高光时刻
路径总和(112)
从根到叶子,是否存在一条路加起来等于 target?
traversal(root, targetSum - root.val);
// 递归时传递剩余值,到叶子时 check 是否为 0
最大/最小深度(104 / 111)
- 最大深度:
1 + max(left, right) - 最小深度:注意单边为空不算叶子!
if (!root.left) return 1 + minDepth(root.right);
if (!root.right) return 1 + minDepth(root.left);
平衡二叉树(110)
每个子树的左右高度差 ≤ 1。用 -1 表示不平衡,提前剪枝!
if (Math.abs(left - right) > 1) return -1;
🧩 第六式:高级玩法 —— 构造 & 修剪 & 累加
从中序+后序建树(106)
- 后序最后一个 = 根
- 在中序中找根,分割左右子树
- 递归构建
🧠 关键:数组切片要精准,别把边界搞错!
修剪 BST(669)
值太小?砍掉左边,去右边找;
值太大?砍掉右边,去左边找;
否则,左右都修!
if (root.val < low) return trimBST(root.right, low, high);
if (root.val > high) return trimBST(root.left, low, high);
累加树(538)—— 反向中序!
从大到小遍历,累加前面的值:
ReverseInOrder(root.right);
cur.val += pre;
pre = cur.val;
ReverseInOrder(root.left);
💡 这叫“右 → 根 → 左”,是中序的镜像!
📊 第七式:统计类问题 —— 中序是你的朋友
最小绝对差(530)
BST 中任意两数最小差?中序遍历后相邻元素差最小!
众数(501)
同样用中序,边遍历边统计频率,动态更新答案数组。
✅ 技巧:不用哈希表!利用 BST 中序有序,相同值会连续出现。
🏁 总结:二叉树刷题心法
| 类型 | 核心思路 | 典型题目 |
|---|---|---|
| 遍历 | 递归三部曲 | 94, 144, 145, 102 |
| 构造 | 分治 + 递归 | 106, 108, 654 |
| BST 操作 | 利用有序性 | 700, 701, 450, 98 |
| 路径/深度 | DFS 回溯 | 112, 104, 111, 110 |
| 统计/转换 | 中序 or 反向中序 | 530, 501, 538 |
🌟 最后送你一句鸡汤:
“树不会说话,但它的结构告诉你一切。”
—— 只要你愿意递归下去,答案终会浮现。