二叉树——路径总和III
题目:
给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。
路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)
这道题的核心要求:
- 路径任意起点、任意终点,只需向下(父->子)
- 统计所有满足节点和 = targetSum 的路径数量
- 经典解法:前缀和 + 深度优先搜索(DFS),时间复杂度O(n),远优于暴力枚举O(n^2)
代码关键点说明
- 数据类型使用long
避免节点值累加时出现整数溢出(int范围有限) - 初始化前缀和{0:1}
处理从根节点开始的有效路径(例如:根节点值 = targetSum) - 回溯操作
递归退出当前节点时,删除当前前缀和的计数,保证哈希表只记录当前路径的前缀和 - 时间复杂度:O (n) 每个节点仅遍历一次
空间复杂度:O (n) 哈希表存储前缀和 + 递归栈空间
代码解释:
int res = prefixMap.getOrDefault(currentSum - targetSum, 0);
一、先拆成 3 个部分
- currentSum
= 从根节点 一路走到 当前节点 的总和 - targetSum
= 你要找的路径和 - currentSum - targetSum
= 我们要在历史记录里找的 “旧总和”
二、核心公式
当前总和 - 某个旧总和 = 目标和
等价于
某个旧总和 = 当前总和 - 目标和
只要历史上出现过这个“旧总和”,就说明:
从那个旧总和的下一个节点 -> 走到当前节点,这一段路径的和正好等于targetSum
三、prefixMap是什么?
它是一个记录“路径总和出现过几次”的map
- key:总和数值
- value:这个总和出现了多少次
比如:
map.put(0, 1) 表示总和 0 出现过 1 次
四、 getOrDefault(..., 0) 意思
prefixMap.getOrDefault(要找的数, 默认值0)
翻译:
去map里查一下:这个“旧总和”出现过几次?
出现过几次,及说明有几条满足条件的路径!
没出现过,就返回0。
用一个例子彻底讲懂
假设:
- 当前走到节点,currentSum = 10
- 你要找 targetSum = 3
那么:
currentSum - targetSum = 10 - 3 = 7
去 map 里查 7 出现过几次:
- 如果出现 2 次 → 说明有 2 条路径 满足和为 3
- 如果没出现 → 返回 0,代表当前节点没有符合的路径
getOrDefault不是哈希表特有,而是Java Map 接口统一提供的方法,所有实现 Map 的集合都能用。
1.它到底是谁的方法?
getOrDefault 是 java.util.Map 接口里的方法:
getOrDefault(Object key, V defaultValue)
所以:
- HashMap ✅ 能用
- TreeMap ✅ 能用
- ConcurrentHashMap ✅ 能用
- 所有实现了 Map 接口的类 ✅ 都能用
不是 HashMap 独有,是整个 Map 体系通用。
2. 作用是什么?
一句话:
有这个 key 就返回对应 value,没有就返回你给的默认值,不会报空指针。
3. 哪些集合没有这个方法?
List(ArrayList、LinkedList)
Set(HashSet、TreeSet)
普通数组
它们都没有 getOrDefault,因为不是键值对结构。
int res = prefixMap.getOrDefault(currentSum - targetSum, 0);
查找currentSum - targetSum这个键对应的值,即旧总和出现的次数,0代表默认值,找不到这个键就返回0
这两行代码的核心意思
// 递归左边
res += dfs(node.left, prefixMap, currentSum, targetSum);
// 递归右边
res += dfs(node.right, prefixMap, currentSum, targetSum);
翻译:
当前节点统计完了,继续去左孩子、右孩子哪里统计,把找到的有效路径数,全部加起来!
1. 先回顾前面的 res 是什么
int res = prefixMap.getOrDefault(currentSum - targetSum, 0);
res = 以当前节点为终点的有效路径数量
2. res += ... 是什么意思?
res += 左子树找到的路径数;
res += 右子树找到的路径数;
意思:
当前节点的路径数 + 左边找到的 + 右边找到的 = 这棵子树下总共的有效路径数
3. 为什么要递归左右孩子?
因为题目要求:
只要是向下的路径都算,不管从哪开始、到哪结束
所以:
- 你不能只算当前这个节点
- 你必须继续往下走,把左子树、右子树里所有满足条件的路径都找出来
4. 递归到底在做什么?
dfs(node.left, ...)
= 把左孩子当成 “当前节点”,重复刚才的逻辑:
- 计算到这个节点的总和
- 查哈希表,看有几条有效路径
- 继续递归它的左右孩子
- 回溯
右孩子同理。
回溯代码的意思:
prefixMap.put(currentSum, prefixMap.get(currentSum) - 1);
1.** prefixMap.get(currentSum)**
拿到当前这个路径和 之前记录的次数
2. -1
次数减 1
= 把刚才我加进去的那一次撤销掉
3. prefixMap.put(...)
把减完之后的次数重新存回哈希表
完整意思
把当前节点的前缀和,从哈希表里 “删掉” 一次。
为什么必须要做这一步?
二叉树是分叉的:
你遍历左子树时,会往哈希表里加记录
等左子树遍历完,要去遍历右子树了****
左子树的记录不能污染右子树!(两个分支完全无关)
所以:
离开当前节点时,必须把它加进哈希表的东西删掉!
这就叫回溯。
最终一句话总结
这行代码的意思:
当前节点遍历完了,把我刚才添加的前缀和记录撤销掉,保证不影响其他分支的计算。
为什么这里用 get,不用 getOrDefault?
因为 这里绝对不可能找不到!
所以 不需要默认值!
什么时候用 getOrDefault?
不确定 key 有没有的时候
比如查历史路径和,可能有、可能没有 → 用 getOrDefault(..., 0)
什么时候用 get?
100% 确定 key 一定存在
比如:
我刚刚才把它放进去!
完整代码实现如下:
import java.util.HashMap;
import java.util.Map;
public class Solution {
public int pathSum(TreeNode root, int targetSum) {
// key:前缀和,value:该前缀和出现的次数
Map<Long, Integer> prefixMap = new HashMap<>();
// 初始化:前缀和为0的情况出现1次(处理从根节点开始的路径)
prefixMap.put(0L, 1);
// 递归遍历
return dfs(root, prefixMap, 0L, targetSum);
}
/**
* @param node 当前遍历节点
* @param prefixMap 前缀和哈希表
* @param currentSum 当前路径和(根到当前节点)
* @param targetSum 目标和
* @return 有效路径数量
*/
private int dfs(TreeNode node, Map<Long, Integer> prefixMap, long currentSum, int targetSum) {
// 递归终止条件:节点为空
if (node == null) {
return 0;
}
// 1. 更新当前路径和
currentSum += node.val;
// 2. 查找满足条件的历史前缀和数量 = 有效路径数
int res = prefixMap.getOrDefault(currentSum - targetSum, 0);
// 3. 将当前前缀和加入哈希表
prefixMap.put(currentSum, prefixMap.getOrDefault(currentSum, 0) + 1);
// 4. 递归遍历左右子树,累加路径数
res += dfs(node.left, prefixMap, currentSum, targetSum);
res += dfs(node.right, prefixMap, currentSum, targetSum);
// 5. 回溯:撤销当前前缀和(关键!避免分支干扰)
prefixMap.put(currentSum, prefixMap.get(currentSum) - 1);
return res;
}
// 二叉树节点定义
public static class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
}
浙公网安备 33010602011771号