Appearance
二叉树
二叉树是算法面试的重中之重,几乎每场面试都会涉及。本模块涵盖二叉树的遍历、深度、翻转、对称、公共祖先、路径和等核心题型。掌握递归与迭代两种遍历方式,理解前序、中序、后序和层序遍历的应用场景,是解决二叉树问题的基础。
Q1: 二叉树的最大深度 「🟢 初级」
题目描述:给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
考察点:递归、树的深度定义、DFS与BFS。
解题思路:
- 方法1:递归DFS,左子树深度和右子树深度的最大值 + 1(时间O(n),空间O(h),h为树高)- 最优解
- 方法2:迭代BFS,层序遍历统计层数(时间O(n),空间O(n))
代码实现:
java
public int maxDepth(TreeNode root) {
if (root == null) return 0;
int left = maxDepth(root.left);
int right = maxDepth(root.right);
return Math.max(left, right) + 1;
}复杂度分析:时间复杂度 O(n),每个节点访问一次;空间复杂度 O(h),递归栈深度为树高,最坏O(n)。
变体扩展:
- 变体1:二叉树的最小深度
- 变体2:平衡二叉树
- 变体3:N叉树的最大深度
- 变体4:二叉树的直径
Q2: 二叉树的层序遍历 「🟡 中级」
题目描述:给你二叉树的根节点 root,返回其节点值的层序遍历。(即逐层地,从左到右访问所有节点)。
考察点:BFS广度优先搜索、队列的应用、层级记录。
解题思路:
- 方法1:BFS迭代,使用队列,每层开始时记录队列大小(时间O(n),空间O(n))- 最优解
- 方法2:DFS递归,用level参数标记当前层级
代码实现:
java
public 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 size = queue.size();
List<Integer> level = new ArrayList<>();
for (int i = 0; i < size; 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;
}复杂度分析:时间复杂度 O(n),每个节点入队出队各一次;空间复杂度 O(n),队列最多存储最后一层所有节点。
变体扩展:
- 变体1:二叉树的锯齿形层序遍历
- 变体2:二叉树的层序遍历II(自底向上)
- 变体3:N叉树的层序遍历
- 变体4:二叉树的右视图
Q3: 翻转二叉树 「🟢 初级」
题目描述:给你一棵二叉树的根节点 root,翻转这棵二叉树,并返回其根节点。
考察点:递归、树的遍历、交换节点。
解题思路:
- 方法1:递归DFS,前序遍历位置交换左右子节点(时间O(n),空间O(h))- 最优解
- 方法2:迭代BFS,层序遍历交换每个节点的左右子节点(时间O(n),空间O(n))
代码实现:
java
public TreeNode invertTree(TreeNode root) {
if (root == null) return null;
// 交换左右子节点
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
// 递归翻转左右子树
invertTree(root.left);
invertTree(root.right);
return root;
}复杂度分析:时间复杂度 O(n),每个节点访问一次;空间复杂度 O(h),递归栈深度。
变体扩展:
- 变体1:对称二叉树
- 变体2:相同的树
- 变体3:另一个树的子树
Q4: 对称二叉树 「🟢 初级」
题目描述:给你一个二叉树的根节点 root,检查它是否轴对称。
考察点:递归、双指针、树的对称性判断。
解题思路:
- 方法1:递归,判断两个子树是否镜像对称(时间O(n),空间O(h))- 最优解
- 方法2:迭代,使用队列成对检查(时间O(n),空间O(n))
代码实现:
java
public boolean isSymmetric(TreeNode root) {
if (root == null) return true;
return isMirror(root.left, root.right);
}
private boolean isMirror(TreeNode t1, TreeNode t2) {
if (t1 == null && t2 == null) return true;
if (t1 == null || t2 == null) return false;
return (t1.val == t2.val)
&& isMirror(t1.left, t2.right)
&& isMirror(t1.right, t2.left);
}复杂度分析:时间复杂度 O(n),遍历整棵树;空间复杂度 O(h),递归栈深度。
变体扩展:
- 变体1:相同的树
- 变体2:翻转二叉树
- 变体3:对称树的迭代实现
Q5: 二叉树的前中后序遍历(递归+迭代) 「🟢 初级」
题目描述:给定一个二叉树的根节点 root,分别返回它的前序、中序、后序遍历。要求分别用递归和迭代两种方式实现。
考察点:二叉树遍历的本质、栈的应用、Morris遍历。
解题思路:
- 方法1:递归,最简单直观(时间O(n),空间O(h))
- 方法2:迭代,使用栈模拟递归(时间O(n),空间O(h))- 需重点掌握
- 方法3:Morris遍历,空间O(1)
代码实现:
java
// 前序遍历 - 迭代
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) return res;
Deque<TreeNode> stack = new ArrayDeque<>();
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;
}
// 中序遍历 - 迭代
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode curr = root;
while (curr != null || !stack.isEmpty()) {
while (curr != null) {
stack.push(curr);
curr = curr.left;
}
curr = stack.pop();
res.add(curr.val);
curr = curr.right;
}
return res;
}
// 后序遍历 - 迭代(前序翻转法)
public List<Integer> postorderTraversal(TreeNode root) {
LinkedList<Integer> res = new LinkedList<>();
if (root == null) return res;
Deque<TreeNode> stack = new ArrayDeque<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
res.addFirst(node.val); // 头插
if (node.left != null) stack.push(node.left);
if (node.right != null) stack.push(node.right);
}
return res;
}复杂度分析:时间复杂度 O(n),每个节点入栈出栈一次;空间复杂度 O(h),栈深度为树高。
变体扩展:
- 变体1:二叉树的层序遍历
- 变体2:从前序与中序遍历序列构造二叉树
- 变体3:从中序与后序遍历序列构造二叉树
- 变体4:Morris遍历
Q6: 二叉树的最近公共祖先 「🟡 中级」
题目描述:给定一个二叉树,找到该树中两个指定节点的最近公共祖先。最近公共祖先的定义为:对于有根树T的两个节点p、q,最近公共祖先表示为一个节点x,满足x是p、q的祖先且x的深度尽可能大。
考察点:递归、后序遍历、祖先定义。
解题思路:
- 方法1:递归后序遍历,返回p或q或公共祖先(时间O(n),空间O(h))- 最优解
- 方法2:存储父节点路径,找第一个公共节点(时间O(n),空间O(n))
代码实现:
java
public 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; // 左右各一个,当前是LCA
return left != null ? left : right; // 都在一边,向上返回
}复杂度分析:时间复杂度 O(n),最坏遍历所有节点;空间复杂度 O(h),递归栈深度。
变体扩展:
- 变体1:二叉搜索树的最近公共祖先
- 变体2:N叉树的最近公共祖先
- 变体3:二叉树的所有公共祖先路径
- 变体4:最小公共祖先II(有父指针)
Q7: 路径总和 「🟢 初级」
题目描述:给你二叉树的根节点 root 和一个表示目标和的整数 targetSum。判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和 targetSum。
考察点:DFS递归、路径定义、回溯思想。
解题思路:
- 方法1:递归DFS,从根到叶逐步减去节点值(时间O(n),空间O(h))- 最优解
- 方法2:BFS迭代,使用两个队列分别存节点和当前和(时间O(n),空间O(n))
代码实现:
java
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) return false;
if (root.left == null && root.right == null) {
return root.val == targetSum;
}
return hasPathSum(root.left, targetSum - root.val)
|| hasPathSum(root.right, targetSum - root.val);
}复杂度分析:时间复杂度 O(n),最坏遍历所有节点;空间复杂度 O(h),递归栈深度。
变体扩展:
- 变体1:路径总和II(返回所有路径)
- 变体2:路径总和III(任意起点终点)
- 变体3:求根到叶子节点数字之和
- 变体4:二叉树的所有路径
Q8: 路径总和III 「🟡 中级」
题目描述:给定一个二叉树的根节点 root,和一个整数 targetSum,求该二叉树里节点值之和等于 targetSum 的路径的数目。路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
考察点:双重递归、前缀和、路径计数。
解题思路:
- 方法1:双重递归,外层遍历每个节点作为起点,内层DFS统计路径数(时间O(n²),空间O(h))
- 方法2:前缀和 + 回溯,类似两数之和思路(时间O(n),空间O(h))- 最优解
代码实现:
java
// 方法1:双重递归
public int pathSum(TreeNode root, int targetSum) {
if (root == null) return 0;
return pathSumFrom(root, targetSum)
+ pathSum(root.left, targetSum)
+ pathSum(root.right, targetSum);
}
private int pathSumFrom(TreeNode node, long sum) {
if (node == null) return 0;
sum -= node.val;
return (sum == 0 ? 1 : 0)
+ pathSumFrom(node.left, sum)
+ pathSumFrom(node.right, sum);
}复杂度分析:双重递归时间复杂度 O(n²),每个节点作为起点都要遍历子树;空间复杂度 O(h)。
变体扩展:
- 变体1:路径总和
- 变体2:路径总和II
- 变体3:路径总和IV
- 变体4:二叉树中的最大路径和
Q9: 验证二叉搜索树 「🟡 中级」
题目描述:给你一个二叉树的根节点 root,判断其是否是一个有效的二叉搜索树。有效二叉搜索树定义如下:节点的左子树只包含小于当前节点的数,节点的右子树只包含大于当前节点的数,所有左子树和右子树自身必须也是二叉搜索树。
考察点:二叉搜索树性质、中序遍历递增、上下界递归。
解题思路:
- 方法1:递归,传入上下界约束(时间O(n),空间O(h))- 最优解
- 方法2:中序遍历,判断是否严格递增(时间O(n),空间O(h))
代码实现:
java
// 递归 - 上下界
public boolean isValidBST(TreeNode root) {
return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE);
}
private boolean isValidBST(TreeNode node, long lower, long upper) {
if (node == null) return true;
if (node.val <= lower || node.val >= upper) return false;
return isValidBST(node.left, lower, node.val)
&& isValidBST(node.right, node.val, upper);
}复杂度分析:时间复杂度 O(n),每个节点访问一次;空间复杂度 O(h),递归栈深度。
变体扩展:
- 变体1:二叉搜索树的中序遍历
- 变体2:恢复二叉搜索树
- 变体3:将有序数组转换为二叉搜索树
- 变体4:二叉搜索树的第k小元素
Q10: 二叉树中的最大路径和 「🔴 高级」
题目描述:二叉树中的路径被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中至多出现一次。该路径至少有一个节点,且不一定经过根节点。路径和是路径中各节点值的总和。给你一个二叉树的根节点 root,返回其最大路径和。
考察点:后序遍历、动态规划思想、最大贡献值。
解题思路:
- 方法:后序遍历递归,计算每个节点的最大贡献值(只能选左右一边),同时更新全局最大路径和(可以左右都选加上当前节点)(时间O(n),空间O(h))- 最优解
代码实现:
java
class Solution {
int maxSum = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
maxGain(root);
return maxSum;
}
private 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);
}
}复杂度分析:时间复杂度 O(n),每个节点访问一次;空间复杂度 O(h),递归栈深度,最坏O(n)。
变体扩展:
- 变体1:二叉树的直径
- 变体2:路径总和III
- 变体3:最长同值路径
- 变体4:二叉树的最大深度