路径总和 III 前缀法核心分析
LeetCode 437. 路径总和 III 的学习
题目链接:LeetCode 437. Path Sum III
🧩 题目描述
给定一个二叉树的根节点 root 和一个整数 targetSum,求 路径和等于 targetSum 的路径个数。路径不需要从根节点开始,也不需要在叶子节点结束,但路径必须向下(只能从父节点走到子节点)。
方法一:暴力 DFS(从每个节点出发)
✅ 思路
- 遍历整棵树,对每个节点都尝试作为起点去找路径。
- 用 DFS 深度遍历每条从当前节点出发的路径,判断路径和是否等于
targetSum。 - 对所有节点都做一遍这个过程,统计结果。
🚫 缺点
- 时间复杂度较高:每个节点都要作为起点做一遍 DFS
- 容易内存超限或超时,尤其是节点值较大时
✅ Java代码
public 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 count = 0;
if (node.val == target) count++;
count += dfs(node.left, target - node.val);
count += dfs(node.right, target - node.val);
return count;
}
}
⏱️ 复杂度分析
- 时间复杂度:O(N²),最差情况是每个节点都遍历一次其所有子节点
- 空间复杂度:O(H),H 为树的高度(递归栈)
方法二:前缀和 + 哈希表优化
✅ 思路
- 维护一个
Map<Long, Integer>表示前缀路径和的出现次数。 - 每次到达一个节点,计算当前从根到这个节点的和
currSum。 - 判断是否存在某个前缀和
currSum - targetSum:- 如果存在,说明从这个前缀节点到当前节点构成一条目标路径。
- 递归左/右子树,并回溯移除当前节点的路径和。
✅ 关键点
-
把树路径转换成“前缀和差值问题”
-
类似数组中找“连续子数组和为 K”的问题
✅ 核心:
map中的 “前缀和” 到底是什么?为什么要存?
🧠 简单类比:数组中的前缀和
先看一维数组:
int[] arr = {1, 2, 3, 4};我们要找连续子数组和等于 5 的个数,我们可以用前缀和:
prefixSum(i) 表示从 arr[0] 到 arr[i] 的总和 prefixSum(j) - prefixSum(i) = arr[i+1] + ... + arr[j]所以,如果你知道某个
prefixSum值在前面出现过,那么就可以快速判断后面有没有子数组和为目标。
🧩 类比到树上
在一棵树中,我们可以把 “从根节点到某个节点的路径和” 看作前缀和。
假设路径如下:
10 / 5 / 3路径:
- 10 -> prefixSum = 10
- 10 + 5 -> prefixSum = 15
- 10 + 5 + 3 -> prefixSum = 18
现在假设我们 target = 8,我们希望找到从某个祖先节点出发,到当前节点,路径和是 8。
- 当前节点 prefixSum 是 18
- 想找从某个祖先到当前路径为 8,就等价于:
18 - x = 8 → x = 10
这说明:如果之前有路径和为 10 的前缀出现过,那这条从该前缀之后到当前节点的路径,一定和为 8!
✅ 所以我们记录什么?
我们用:
Map<Long, Integer> map记录:
- 键:从根到某个祖先节点的路径和
- 值:这个路径和出现了多少次(因为多条路径可能产生相同前缀和)
✅ 用例演示(一步步)
输入树:
10 / \ 5 -3 / \ \ 3 2 11目标
target = 8我们 DFS 遍历树,每走一个节点,就计算到该节点的
currSum,并:- 查找
map.get(currSum - target),看前面有没有出现过这个“前缀和” - 如果有,那就说明有路径和 = target
✅ 示例代码中的关键一行:
int res = map.getOrDefault(currSum - targetSum, 0);它的含义是:
当前节点能否通过某个祖先节点构成一条路径和为 target 的路径?
如果有,就说明路径数量可以加上。
✅ Java代码
public class Solution {
public int pathSum(TreeNode root, int targetSum) {
Map<Long, Integer> map = new HashMap<>();
map.put(0L, 1); // base case
return dfs(root, 0L, targetSum, map);
}
private int dfs(TreeNode node, long currSum, int targetSum, Map<Long, Integer> map) {
if (node == null) return 0;
currSum += node.val;
int count = map.getOrDefault(currSum - targetSum, 0);
map.put(currSum, map.getOrDefault(currSum, 0) + 1);
count += dfs(node.left, currSum, targetSum, map);
count += dfs(node.right, currSum, targetSum, map);
map.put(currSum, map.get(currSum) - 1); // 回溯
return count;
}
}
⏱️ 复杂度分析
- 时间复杂度:O(N),每个节点只访问一次
- 空间复杂度:O(N),哈希表和递归栈的最大空间
🧠 总结对比
| 方法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 暴力 DFS | O(N²) | O(H) | 思路简单 | 性能差,易超时 |
| 前缀和优化法 | O(N) | O(N) | 性能好,易扩展 | 思路抽象,需要理解 |

浙公网安备 33010602011771号