437. 路径总和 III#
现实映射:宝藏地图的密码破解#
考古学家发现了一张古老的宝藏地图,地图上标注了一棵神秘的二叉树,每个节点都藏有一个数字密码。为了找到宝藏,我们需要计算从任意节点出发,向下追踪路径,使得路径上的数字之和恰好等于目标值的所有可能路径。
这正是我们今天要解决的算法难题——路径总和 III!
问题描述#
题目目标#
LeetCode第437题要求:给定一个二叉树的根节点 root 和一个整数 targetSum,返回该二叉树中路径和等于 targetSum 的路径数目。路径不需要从根节点开始,也不需要在叶子节点结束,但必须是从父节点到子节点的方向。
示例 1#
输入: root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
输出: 3
二叉树示意:
10
/ \
5 -3
/ \ \
3 2 11
/ \ \
3 -2 1text说明: 路径1:5 → 3 路径2:5 → 2 → 1 路径3:-3 → 11
补充说明#
- 二叉树通常使用层序数组表示,
null表示该位置没有节点。
直觉解法:双重递归#
就像考古学家需要逐层挖掘和逐点分析,我们可以用双重递归的策略来解决这个问题:
- 外层递归:遍历每个节点,作为路径的起点
- 内层递归:从当前节点出发,向下搜索所有可能的路径,并计算路径和
Java实现#
class Solution {
public int pathSum(TreeNode root, int targetSum) {
// 边界处理:空树中不存在任何路径
if (root == null) return 0;
// 总路径数 =
// 1) 以当前节点为起点的合法路径数
// 2) 左子树内部的合法路径数
// 3) 右子树内部的合法路径数
return countPath(root, targetSum)
+ pathSum(root.left, targetSum)
+ pathSum(root.right, targetSum);
}
private int countPath(TreeNode node, long targetSum) {
// 递归终止:空节点不产生路径
if (node == null) return 0;
// 记录“以当前节点为起点”时,满足条件的路径数量
int count = 0;
// 如果当前节点值刚好等于剩余目标值,则找到一条路径
if (node.val == targetSum) {
count++;
}
// 继续向左搜索,新的目标值要减去当前节点值
count += countPath(node.left, targetSum - node.val);
// 继续向右搜索,新的目标值同样减去当前节点值
count += countPath(node.right, targetSum - node.val);
// 返回从当前节点出发可形成的路径总数
return count;
}
}java复杂度分析
时间复杂度:O(n²)(每个节点作为起点,最坏情况下需要遍历整棵树)
空间复杂度:O(n)(递归栈深度)
忍者解法:前缀和与哈希表的奥义#
真正的考古大师会记录每一步的线索!通过使用前缀和和哈希表,我们可以将时间复杂度优化到线性级别。
核心优化点#
- 前缀和:记录从根节点到当前节点的路径和
- 哈希表:存储前缀和的出现次数,快速查找是否存在满足条件的路径
关键步骤演示(以示例说明)#
目标值:8
前缀和哈希表:{0:1} (初始化)
构建过程:
1. 根节点10,前缀和10 → 查找10-8=2,不存在
→ 更新哈希表:{0:1, 10:1}
2. 左子节点5,前缀和15 → 查找15-8=7,不存在
→ 更新哈希表:{0:1, 10:1, 15:1}
3. 左子节点3,前缀和18 → 查找18-8=10,存在(计数+1)
→ 更新哈希表:{0:1, 10:1, 15:1, 18:1}
...plaintextJava实现#
class Solution {
public int pathSum(TreeNode root, int targetSum) {
// 前缀和哈希表:key=前缀和,value=该前缀和出现次数
Map<Long, Integer> prefixSumMap = new HashMap<>();
// 初始化:前缀和为 0 出现 1 次(表示“从根之前开始”)
prefixSumMap.put(0L, 1); // 初始化
// 从根节点开始 DFS,currentSum 初始为 0
return dfs(root, 0L, targetSum, prefixSumMap);
}
private int dfs(TreeNode node, long currentSum, int targetSum, Map<Long, Integer> prefixSumMap) {
// 递归终止:空节点贡献路径数为 0
if (node == null) return 0;
// 将当前节点值加入路径和,得到“根到当前节点”的前缀和
currentSum += node.val; // 更新当前路径和
// 若存在某个历史前缀和 = currentSum - targetSum,
// 则“历史节点之后到当前节点”这段路径和恰好为 targetSum
int count = prefixSumMap.getOrDefault(currentSum - targetSum, 0); // 查找满足条件的路径
// 将当前前缀和计入哈希表,供子树节点使用
prefixSumMap.put(currentSum, prefixSumMap.getOrDefault(currentSum, 0) + 1);
// 递归统计左子树中的合法路径
count += dfs(node.left, currentSum, targetSum, prefixSumMap);
// 递归统计右子树中的合法路径
count += dfs(node.right, currentSum, targetSum, prefixSumMap);
// 回溯:离开当前节点前,撤销当前前缀和出现次数
// 避免影响兄弟分支的统计结果
prefixSumMap.put(currentSum, prefixSumMap.get(currentSum) - 1);
// 返回以当前节点为根的子树中路径总数
return count;
}
}java复杂度分析
时间复杂度:O(n)(只需遍历一次树)
空间复杂度:O(n)(哈希表存储)
解法对比#
| 方法 | 时间复杂度 | 空间复杂度 | 优势 |
|---|---|---|---|
| 双重递归 | O(n²) | O(n) | 实现简单 |
| 前缀和+哈希表 | O(n) | O(n) | 时间效率最优 |
模式总结#
本题体现了两个关键算法思想:
- 前缀和:通过记录累加和,快速计算任意区间的和
- 哈希表优化:通过空间换时间,将查找操作优化到常数级别
这种模式可以扩展到:
- 数组中子数组和等于目标值的问题
- 其他需要快速计算区间和的场景
考古大师心法#
优秀的算法设计就像破解宝藏密码:
- 记录线索:通过前缀和记录每一步的路径和
- 快速查找:利用哈希表快速定位满足条件的路径
- 回溯清理:在递归返回时恢复状态,避免干扰其他路径
记住:当遇到需要频繁计算区间和或路径和的场景时,不妨先问问自己——能否通过前缀和和哈希表来优化查找效率?