二叉树——路径总和III

题目:
给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。
路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)

这道题的核心要求:

  1. 路径任意起点、任意终点,只需向下(父->子)
  2. 统计所有满足节点和 = targetSum 的路径数量
  3. 经典解法:前缀和 + 深度优先搜索(DFS),时间复杂度O(n),远优于暴力枚举O(n^2)

代码关键点说明

  1. 数据类型使用long
    避免节点值累加时出现整数溢出(int范围有限)
  2. 初始化前缀和{0:1}
    处理从根节点开始的有效路径(例如:根节点值 = targetSum)
  3. 回溯操作
    递归退出当前节点时,删除当前前缀和的计数,保证哈希表只记录当前路径的前缀和
  4. 时间复杂度:O (n) 每个节点仅遍历一次
    空间复杂度:O (n) 哈希表存储前缀和 + 递归栈空间

代码解释:
int res = prefixMap.getOrDefault(currentSum - targetSum, 0);

一、先拆成 3 个部分

  1. currentSum
    = 从根节点 一路走到 当前节点 的总和
  2. targetSum
    = 你要找的路径和
  3. 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, ...)
= 把左孩子当成 “当前节点”,重复刚才的逻辑:

  1. 计算到这个节点的总和
  2. 查哈希表,看有几条有效路径
  3. 继续递归它的左右孩子
  4. 回溯
    右孩子同理。

回溯代码的意思:
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;
    }
}

}

posted @ 2026-04-23 22:40  AlexXuu  阅读(7)  评论(0)    收藏  举报