LeetCode 437. 路径总和 III(前缀和 + DFS 回溯法)

0 阅读3分钟

image.png

image.png

一、核心思路

题目要求:路径方向只能向下,路径不用从根开始、不用到叶子节点,统计节点和等于targetSum的路径总数。

前缀和定义:从根节点走到当前节点,沿途节点累加总和。 公式:当前前缀和 − 祖先前缀和 = 两点之间的路径和 变形:若 当前前缀和 - targetSum 在哈希表存在,说明存在一段路径和等于targetSum。 HashMap:记录当前递归路径上前缀和出现次数;递归返回时回溯,撤销本次新增前缀和,防止左右分支数据互相污染。

二、完整 Java 代码

/**
 * Definition for a binary tree node.
 * public 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;
 *     }
 * }
 */
class Solution {
    public int pathSum(TreeNode root, int targetSum) {
        Map<Long,Integer> map = new HashMap<>();
        map.put(0L,1); // 预设虚拟前缀和0,处理从根出发直接满足条件的路径
        return dfs(root, map, targetSum, 0L);
    }
    
    int dfs(TreeNode node, Map<Long, Integer> map, int target, long currSum) {
        if(node == null)    return 0;
        int res = 0;
        // 步骤1:更新当前前缀和
        currSum += node.val;
        // 步骤2:查表,统计符合条件路径数量
        res += map.getOrDefault(currSum - target, 0);
        // 步骤3:将当前前缀和存入map,供子节点查询
        map.put(currSum, map.getOrDefault(currSum, 0) + 1);
        // 步骤4:递归遍历左右子树
        res += dfs(node.left, map, target, currSum);
        res += dfs(node.right, map, target, currSum);
        // 步骤5:回溯!离开节点前撤销本次前缀和记录
        map.put(currSum, map.get(currSum) - 1);
        return res;
    }       
}

三、实例演示

样例输入:root = [10,5,-3,3,2,null,11,3,-2,null,1],targetSum = 8 树结构:

        10
      /    \
     5      -3
    / \       \
   3   2       11
  / \   \
 3  -2   1

答案:共 3 条路径:5→3、5→2→1、-3→11

递归访问节点当前前缀和 currSum查找 currSum-8map 内存储的前缀和(仅当前根到节点路径)新增路径数量res 累计
10102{0:1}00
5157{0:1,10:1}00
31810{0:1,10:1,15:1}11
32113{0:1,10:1,15:1,18:1}01回溯,21 计数 - 1
-2168{0:1,10:1,15:1,18:1}01回溯,16 计数 - 1回溯,18 计数 - 1
2179{0:1,10:1,15:1}01
11810{0:1,10:1,15:1,17:1}12回溯,18 计数 - 1回溯,17 计数 - 1回溯,15 计数 - 1
-37-1{0:1,10:1}02
111810{0:1,10:1,7:1}13回溯,18 计数 - 1回溯,7 计数 - 1回溯,10 计数 - 1

四、复杂度分析

  • 时间复杂度:O (n),n 为节点数量,每个节点访问一次
  • 空间复杂度:O (n),递归栈深度 + 哈希表存储前缀和,最坏为单链树

五、易错点总结

  1. 初始化map.put(0L,1):虚拟前缀和,用于匹配从根节点开始就满足总和等于 targetSum 的路径。
  2. 回溯不可省略:左子树遍历完成后,需要撤销 map 中当前节点的前缀和,防止右分支读取左分支残留数据,造成计数错误。
  3. 前缀和变量必须使用long:节点值累加可能超出 int 范围,引发溢出;同时匹配Map<Long,Integer>的 key 类型,避免类型转换报错。
  4. 变量拼写:currSum,不要写成 currtSum,会报找不到符号的编译错误。
  5. map 维护的是当前单条从上到下路径的前缀和,不是整棵树所有节点的前缀和。

六、个人感悟

这道题是数组前缀和思想迁移到二叉树上。二叉树深度优先搜索,一条递归链等价于数组;但是二叉树存在左右分叉,所以递归返回时要做回溯,保证哈希表只保存当前根到当前节点这条路径上的前缀和,左右分支互不干扰。