1. 二叉树基础概念与核心特性
二叉树是每个节点最多有两个子节点的树形数据结构,这种看似简单的结构却蕴含着强大的表达能力。在实际编码面试和算法竞赛中,二叉树相关题目占比超过30%,是每个程序员必须攻克的核心数据结构。
1.1 二叉树的数学本质
从数学角度看,二叉树是一个连通无环图,满足以下条件:
- 每个节点最多有两个子节点(左子节点和右子节点)
- 除根节点外,每个节点有且只有一个父节点
- 空树也是合法的二叉树
这种结构天然适合使用递归处理,因为每个子树本身也是二叉树。递归三要素在二叉树中体现得尤为明显:
- 递归终止条件:通常为节点为空(null)
- 当前层处理逻辑:访问/修改当前节点
- 递归调用:处理左子树和右子树
1.2 二叉树的五种基本形态
理解二叉树的形态对解题思路有直接影响:
- 空树:没有任何节点的二叉树
- 只有根节点:没有子节点的单节点树
- 只有左子树的二叉树
- 只有右子树的二叉树
- 左右子树都存在的完整二叉树
在算法题中,经常需要处理各种形态的组合。例如,平衡二叉树的旋转操作就涉及多种形态转换。
1.3 二叉树的重要性质
掌握这些性质能快速判断算法可行性:
- 第i层最多有2^(i-1)个节点(i≥1)
- 深度为k的二叉树最多有2^k -1个节点(k≥1)
- 任何二叉树,叶节点数n0与度为2的节点数n2满足:n0 = n2 + 1
- 具有n个节点的完全二叉树深度为⌊log₂n⌋+1
提示:在解决二叉树深度相关问题时,记住"完全二叉树的深度计算"这个公式能节省大量推导时间。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 二叉树的遍历:算法基石与实战变种
二叉树的遍历是几乎所有相关算法的基础,必须做到闭眼也能写出无bug的代码。遍历方式根据访问根节点的顺序分为三类,每类又有递归和迭代两种实现方式。
2.1 基础遍历方式对比
| 遍历类型 | 访问顺序 | 典型应用场景 | 时间复杂度 |
|---|---|---|---|
| 前序遍历 | 根→左→右 | 树的结构复制、表达式树求值 | O(n) |
| 中序遍历 | 左→根→右 | 二叉搜索树的有序输出 | O(n) |
| 后序遍历 | 左→右→根 | 树的销毁、计算子树属性 | O(n) |
| 层序遍历 | 按层次从上到下 | 计算树宽度、找最大深度 | O(n) |
2.2 递归实现模板(以Java为例)
java复制// 前序遍历
void preorder(TreeNode root) {
if (root == null) return;
System.out.print(root.val + " "); // 处理当前节点
preorder(root.left);
preorder(root.right);
}
// 中序遍历
void inorder(TreeNode root) {
if (root == null) return;
inorder(root.left);
System.out.print(root.val + " "); // 处理当前节点
inorder(root.right);
}
// 后序遍历
void postorder(TreeNode root) {
if (root == null) return;
postorder(root.left);
postorder(root.right);
System.out.print(root.val + " "); // 处理当前节点
}
2.3 迭代实现要点
递归实现虽然简洁,但在处理大型树时可能栈溢出。迭代实现通常使用栈模拟递归:
java复制// 前序遍历迭代版
List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Deque<TreeNode> stack = new ArrayDeque<>();
if (root != null) stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
res.add(node.val);
// 右子节点先入栈,保证左子节点先处理
if (node.right != null) stack.push(node.right);
if (node.left != null) stack.push(node.left);
}
return res;
}
层序遍历则使用队列实现,是BFS的典型应用:
java复制List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if (root == null) return res;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int levelSize = queue.size();
List<Integer> level = new ArrayList<>();
for (int i = 0; i < levelSize; i++) {
TreeNode node = queue.poll();
level.add(node.val);
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
res.add(level);
}
return res;
}
2.4 遍历的实战变种
真实面试中常出现遍历变种题,例如:
- Zigzag层序遍历(奇数层从左到右,偶数层从右到左)
- 垂序遍历(按列输出节点)
- 边界遍历(输出树的左边界、叶节点、右边界)
以Zigzag遍历为例,只需在层序遍历基础上增加方向标志:
java复制List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if (root == null) return res;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
boolean leftToRight = true;
while (!queue.isEmpty()) {
int size = queue.size();
LinkedList<Integer> level = new LinkedList<>();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
if (leftToRight) {
level.addLast(node.val);
} else {
level.addFirst(node.val);
}
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
res.add(level);
leftToRight = !leftToRight;
}
return res;
}
3. 二叉搜索树:高效查找的结构特性
二叉搜索树(BST)是一种特殊的二叉树,它极大地提高了查找效率,平均时间复杂度为O(log n)。理解BST的性质对解决相关问题至关重要。
3.1 BST的定义与验证
BST的定义:
- 左子树所有节点值小于根节点值
- 右子树所有节点值大于根节点值
- 左右子树也必须是BST
验证BST的常见误区是只比较节点与直接子节点。正确做法是传递值范围:
java复制boolean isValidBST(TreeNode root) {
return validate(root, Long.MIN_VALUE, Long.MAX_VALUE);
}
boolean validate(TreeNode node, long min, long max) {
if (node == null) return true;
if (node.val <= min || node.val >= max) return false;
return validate(node.left, min, node.val)
&& validate(node.right, node.val, max);
}
3.2 BST基本操作
3.2.1 查找
利用BST性质可以快速定位目标:
java复制TreeNode searchBST(TreeNode root, int val) {
while (root != null && root.val != val) {
root = val < root.val ? root.left : root.right;
}
return root;
}
3.2.2 插入
新节点总是作为叶节点插入:
java复制TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null) return new TreeNode(val);
if (val < root.val) {
root.left = insertIntoBST(root.left, val);
} else {
root.right = insertIntoBST(root.right, val);
}
return root;
}
3.2.3 删除
删除操作较为复杂,需要考虑三种情况:
- 目标节点是叶节点:直接删除
- 目标节点有一个子节点:用子节点替代
- 目标节点有两个子节点:用后继节点(右子树的最小值)替代
java复制TreeNode deleteNode(TreeNode root, int key) {
if (root == null) return null;
if (key < root.val) {
root.left = deleteNode(root.left, key);
} else if (key > root.val) {
root.right = deleteNode(root.right, key);
} else {
if (root.left == null) return root.right;
if (root.right == null) return root.left;
TreeNode minNode = findMin(root.right);
root.val = minNode.val;
root.right = deleteNode(root.right, root.val);
}
return root;
}
TreeNode findMin(TreeNode node) {
while (node.left != null) {
node = node.left;
}
return node;
}
3.3 BST与中序遍历的关系
BST的中序遍历结果是一个升序数组,这一性质被广泛应用于相关问题:
- 恢复错误的BST:找出中序遍历中逆序的节点
- 第k小元素:中序遍历到第k个节点
- 范围查询:中序遍历时收集在[min,max]区间内的值
以第k小元素为例:
java复制int kthSmallest(TreeNode root, int k) {
Deque<TreeNode> stack = new ArrayDeque<>();
while (root != null || !stack.isEmpty()) {
while (root != null) {
stack.push(root);
root = root.left;
}
root = stack.pop();
if (--k == 0) return root.val;
root = root.right;
}
return -1;
}
4. 二叉树高频题型与解题框架
经过对LeetCode等平台数百道二叉树题目的分析,可以总结出几类高频题型及对应的解题框架。
4.1 路径相关问题
这类问题通常要求找到满足特定条件的路径,如:
- 路径总和等于给定值
- 最长直径路径
- 最大路径和
解题框架:
- 定义递归函数返回需要的信息
- 处理当前节点
- 递归处理左右子树
- 结合左右子树结果处理当前节点
以"二叉树中的最大路径和"为例:
java复制int maxSum = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
maxGain(root);
return maxSum;
}
int maxGain(TreeNode node) {
if (node == null) return 0;
// 计算左右子树的最大贡献值
int leftGain = Math.max(maxGain(node.left), 0);
int rightGain = Math.max(maxGain(node.right), 0);
// 当前节点作为转折点的路径和
int priceNewpath = node.val + leftGain + rightGain;
maxSum = Math.max(maxSum, priceNewpath);
// 返回当前节点的最大贡献值
return node.val + Math.max(leftGain, rightGain);
}
4.2 子树与子结构问题
判断树B是否是树A的子结构/子树是常见题型。注意子结构与子树的区别:
- 子树:必须从某个节点开始完全匹配
- 子结构:部分匹配即可
子树判断框架:
java复制boolean isSubtree(TreeNode root, TreeNode subRoot) {
if (root == null) return subRoot == null;
return isSameTree(root, subRoot)
|| isSubtree(root.left, subRoot)
|| isSubtree(root.right, subRoot);
}
boolean isSameTree(TreeNode p, TreeNode q) {
if (p == null && q == null) return true;
if (p == null || q == null) return false;
return p.val == q.val
&& isSameTree(p.left, q.left)
&& isSameTree(p.right, q.right);
}
4.3 构造二叉树问题
根据遍历序列重建二叉树是考察对遍历顺序理解的典型问题。常见组合:
- 前序+中序
- 后序+中序
- 层序+中序
以前序+中序为例:
java复制Map<Integer, Integer> indexMap = new HashMap<>();
TreeNode buildTree(int[] preorder, int[] inorder) {
for (int i = 0; i < inorder.length; i++) {
indexMap.put(inorder[i], i);
}
return helper(preorder, 0, preorder.length - 1, 0);
}
TreeNode helper(int[] preorder, int preLeft, int preRight, int inLeft) {
if (preLeft > preRight) return null;
int rootVal = preorder[preLeft];
TreeNode root = new TreeNode(rootVal);
int inRoot = indexMap.get(rootVal);
int leftSize = inRoot - inLeft;
root.left = helper(preorder, preLeft + 1, preLeft + leftSize, inLeft);
root.right = helper(preorder, preLeft + leftSize + 1, preRight, inRoot + 1);
return root;
}
4.4 最近公共祖先(LCA)问题
寻找两个节点的最近公共祖先有多种解法,最优的是后序遍历法:
java复制TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root == p || root == q) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if (left != null && right != null) return root;
return left != null ? left : right;
}
提示:当处理树的问题时,先思考空节点情况总是没错的。这能避免许多边界条件错误。
