作为一名在算法领域摸爬滚打多年的老手,我深知二叉树是面试中最常考的数据结构之一。今天这篇笔记将系统梳理LeetCode上5个经典二叉树问题,不仅给出解法,更会深入剖析每种解法的设计思路和优化逻辑。这些题目覆盖了二叉树遍历、构造、路径计算等核心考点,掌握它们能帮你建立起完整的二叉树解题框架。
这个解法采用前序遍历思路,通过三步完成展开:
关键点在于需要找到左子树的最右节点,这样才能正确连接原右子树。时间复杂度O(n),空间复杂度O(h)(递归栈空间)。
java复制class Solution {
public void flatten(TreeNode root) {
if (root == null) return;
flatten(root.left);
flatten(root.right);
TreeNode tempRight = root.right;
root.right = root.left;
root.left = null;
TreeNode current = root;
while (current.right != null) {
current = current.right;
}
current.right = tempRight;
}
}
注意:在移动左子树前一定要保存原右子树指针,否则会导致指针丢失。这是新手常犯的错误。
这种解法巧妙地利用后序遍历顺序(右-左-根)和全局变量prev:
java复制class Solution {
private TreeNode prev = null;
public void flatten(TreeNode root) {
if (root == null) return;
flatten(root.right);
flatten(root.left);
root.right = prev;
root.left = null;
prev = root;
}
}
两种解法的对比:
这道题考察对二叉树遍历性质的深入理解。前序遍历的第一个元素总是根节点,而中序遍历中根节点左侧就是左子树,右侧是右子树。
优化点:使用HashMap存储中序遍历的值到索引的映射,可以将查找操作从O(n)降到O(1)。
java复制class Solution {
private Map<Integer, Integer> inMap;
public TreeNode buildTree(int[] preorder, int[] inorder) {
inMap = new HashMap<>();
for (int i = 0; i < inorder.length; i++) {
inMap.put(inorder[i], i);
}
return build(preorder, 0, preorder.length-1,
inorder, 0, inorder.length-1);
}
private TreeNode build(int[] pre, int preL, int preR,
int[] in, int inL, int inR) {
if (preL > preR) return null;
int rootVal = pre[preL];
TreeNode root = new TreeNode(rootVal);
int mid = inMap.get(rootVal);
int leftLen = mid - inL;
root.left = build(pre, preL+1, preL+leftLen,
in, inL, mid-1);
root.right = build(pre, preL+leftLen+1, preR,
in, mid+1, inR);
return root;
}
}
递归终止条件是preL > preR,表示当前子树区间为空。计算左子树长度leftLen时要注意:
常见错误:
这是最优解法,时间复杂度O(n),空间复杂度O(n)。核心思想是利用前缀和差等于目标值的特点。
java复制class Solution {
private Map<Long, Integer> preMap;
private int count;
public int pathSum(TreeNode root, int targetSum) {
preMap = new HashMap<>();
preMap.put(0L, 1); // 初始前缀和为0出现1次
count = 0;
dfs(root, 0, targetSum);
return count;
}
private void dfs(TreeNode node, long sum, int target) {
if (node == null) return;
sum += node.val;
count += preMap.getOrDefault(sum - target, 0);
preMap.put(sum, preMap.getOrDefault(sum, 0) + 1);
dfs(node.left, sum, target);
dfs(node.right, sum, target);
preMap.put(sum, preMap.get(sum) - 1); // 回溯
}
}
关键细节:
虽然时间复杂度O(n²)较高,但代码更直观,适合作为思考起点:
java复制class Solution {
public int pathSum(TreeNode root, int targetSum) {
if (root == null) return 0;
return dfs(root, targetSum)
+ pathSum(root.left, targetSum)
+ pathSum(root.right, targetSum);
}
private int dfs(TreeNode node, long target) {
if (node == null) return 0;
int res = 0;
if (node.val == target) res++;
res += dfs(node.left, target - node.val);
res += dfs(node.right, target - node.val);
return res;
}
}
这是二叉树问题的经典解法,采用后序遍历框架:
java复制class Solution {
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) return right;
if (right == null) return left;
return root;
}
}
算法逻辑:
虽然递归解法简洁,但了解迭代解法也很重要。可以使用父指针记录每个节点的父节点,然后找到p和q的第一个公共祖先。
这道题是二叉树问题的巅峰之作,需要同时考虑局部和全局最优解:
java复制class Solution {
private int maxSum = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
dfs(root);
return maxSum;
}
private int dfs(TreeNode node) {
if (node == null) return 0;
int leftGain = Math.max(dfs(node.left), 0);
int rightGain = Math.max(dfs(node.right), 0);
int currentPathSum = node.val + leftGain + rightGain;
maxSum = Math.max(maxSum, currentPathSum);
return node.val + Math.max(leftGain, rightGain);
}
}
关键点:
虽然这个解法已经很高效,但在实际应用中还可以:
经过多年刷题和面试经验,我总结出二叉树问题的通用解法框架:
对于想系统学习算法的同学,建议:
在实际面试中,二叉树问题常作为中等难度题目出现。写出正确代码只是基础,面试官更看重:
记住:理解比死记硬背更重要。真正掌握这些算法的精髓,才能在面试中灵活应变。