LeetCode路径总和系列问题解析:I、II、III的解决方案与优化
文章目录
- 引言
- 一、路径总和 I(LeetCode 112)
- 问题描述
- 方法思路
- Java代码实现
- 复杂度分析
- 二、路径总和 II(LeetCode 113)
- 问题描述
- 方法思路
- Java代码实现
- 复杂度分析
- 三、路径总和 III(LeetCode 437)
- 问题描述
- 方法思路
- Java代码实现
- 复杂度分析
- 四、对比与总结
- 方法对比
- 总结
- 五、示例验证
- 路径总和II示例
- 路径总和III示例
引言
路径总和系列是二叉树遍历中的经典问题,涵盖从基础递归到高级优化的多种解法。本文详细分析LeetCode中路径总和I、II、III的解题思路,并提供Java实现代码与优化技巧,帮助读者深入理解二叉树遍历与回溯算法的应用。
一、路径总和 I(LeetCode 112)
问题描述
判断二叉树中是否存在从根节点到叶子节点的路径,使得路径节点值之和等于给定目标数。
方法思路
- 递归遍历:深度优先搜索(DFS)遍历每个节点。
- 终止条件:
- 空节点直接返回
false
。 - 叶子节点判断当前剩余值是否等于节点值。
- 空节点直接返回
- 递归逻辑:对左右子树递归检查剩余目标值。
Java代码实现
class Solution {public boolean hasPathSum(TreeNode root, int targetSum) {if (root == null) return false;if (root.left == null && root.right == null) {return targetSum == root.val;}return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val);}
}
复杂度分析
- 时间复杂度:O(n),每个节点访问一次。
- 空间复杂度:O(n),递归栈深度在最坏情况下(树退化为链表)为n。
二、路径总和 II(LeetCode 113)
问题描述
找出所有从根节点到叶子节点路径和等于目标数的路径,返回路径列表。
方法思路
- 回溯法:通过动态维护路径列表记录当前路径。
- 关键步骤:
- 添加当前节点到路径。
- 到达叶子节点时检查路径和。
- 递归返回前移除当前节点(回溯)。
Java代码实现
class Solution {public List<List<Integer>> pathSum(TreeNode root, int targetSum) {List<List<Integer>> result = new ArrayList<>();dfs(root, targetSum, new ArrayList<>(), result);return result;}private void dfs(TreeNode node, int remain, List<Integer> path, List<List<Integer>> result) {if (node == null) return;path.add(node.val);if (node.left == null && node.right == null && remain == node.val) {result.add(new ArrayList<>(path)); // 深拷贝路径} else {dfs(node.left, remain - node.val, path, result);dfs(node.right, remain - node.val, path, result);}path.remove(path.size() - 1); // 回溯}
}
复杂度分析
- 时间复杂度:O(n),每个节点访问一次。
- 空间复杂度:O(n²),存储所有路径的空间开销。
三、路径总和 III(LeetCode 437)
问题描述
统计路径和等于目标数的路径数量。路径方向必须向下,但起点和终点不限制。
方法思路
- 前缀和+哈希表优化:
- 记录路径前缀和及其出现次数。
- 若当前前缀和为
currentSum
,查找currentSum - target
是否存在。
- 回溯维护:递归后需清理当前前缀和,避免影响其他子树。
Java代码实现
class Solution {private int count = 0;private Map<Long, Integer> prefixMap = new HashMap<>();public int pathSum(TreeNode root, int targetSum) {prefixMap.put(0L, 1); // 初始化空路径dfs(root, 0L, targetSum);return count;}private void dfs(TreeNode node, long currentSum, int target) {if (node == null) return;currentSum += node.val;count += prefixMap.getOrDefault(currentSum - target, 0);prefixMap.put(currentSum, prefixMap.getOrDefault(currentSum, 0) + 1);dfs(node.left, currentSum, target);dfs(node.right, currentSum, target);prefixMap.put(currentSum, prefixMap.get(currentSum) - 1); // 回溯}
}
复杂度分析
- 时间复杂度:O(n),每个节点访问一次。
- 空间复杂度:O(n),哈希表存储前缀和。
四、对比与总结
方法对比
问题 | 核心方法 | 时间复杂度 | 空间复杂度 | 关键难点 |
---|---|---|---|---|
路径总和 I | 递归遍历 | O(n) | O(n) | 终止条件判断 |
路径总和 II | 回溯+路径记录 | O(n) | O(n²) | 深拷贝与回溯逻辑 |
路径总和 III | 前缀和+哈希表 | O(n) | O(n) | 前缀和统计与回溯维护 |
总结
- 路径总和I:基础递归应用,理解终止条件与递归分解。
- 路径总和II:掌握回溯法维护路径状态,注意深拷贝避免引用问题。
- 路径总和III:前缀和优化是核心,通过空间换时间避免重复计算。
五、示例验证
路径总和II示例
输入:
5/ \4 8/ / \11 13 4/ \ / \7 2 5 1
目标值:22
输出:[[5,4,11,2], [5,8,4,5]]
路径总和III示例
输入:
10/ \5 -3/ \ \3 2 11/ \ \
3 -2 1
目标值:8
输出:3(路径为5→3
, 5→2→1
, -3→11
)
通过系统化分析与代码实现,读者可深入掌握二叉树路径问题的多种解法,提升算法设计与优化能力。