【刷穿 LeetCode】437. 路径总和 III :「DFS」&「前缀和」

简介: 【刷穿 LeetCode】437. 路径总和 III :「DFS」&「前缀和」

网络异常,图片无法展示
|


题目描述



这是 LeetCode 上的 437. 路径总和 III ,难度为 中等


Tag : 「DFS」、「树的遍历」、「前缀和」


给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。


路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。


示例 1:


网络异常,图片无法展示
|


输入:root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
输出:3
解释:和等于 8 的路径有 3 条,如图所示。
复制代码


示例 2:


输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:3
复制代码


提示:


  • 二叉树的节点个数的范围是 [0,1000]
  • -10^9109 <= Node.val <= 10^9109
  • -1000 <= targetSum <= 1000


树的遍历 + DFS



一个朴素的做法是搜索以每个节点为根的(往下的)所有路径,并对路径总和为 targetSumtargetSum 的路径进行累加统计。


使用 dfs1 来搜索所有节点,复杂度为 O(n)O(n);在 dfs1 中对于每个当前节点,使用 dfs2 搜索以其为根的所有(往下的)路径,同时累加路径总和为 targetSumtargetSum 的所有路径,复杂度为 O(n)O(n)


整体复杂度为 O(n^2)O(n2),数据范围为 10^3103,可以过。


代码:


class Solution {
    int ans, t;
    public int pathSum(TreeNode root, int _t) {
        t = _t;
        dfs1(root);
        return ans;
    }
    void dfs1(TreeNode root) {
        if (root == null) return;
        dfs2(root, root.val);
        dfs1(root.left);
        dfs1(root.right);
    }
    void dfs2(TreeNode root, int val) {
        if (val == t) ans++;
        if (root.left != null) dfs2(root.left, val + root.left.val);
        if (root.right != null) dfs2(root.right, val + root.right.val);
    }
}
复制代码


  • 时间复杂度:O(n^2)O(n2)
  • 空间复杂度:忽略递归带来的额外空间开销,复杂度为 O(1)O(1)


树的遍历 + 前缀和



在「解法一」中,我们统计的是以每个节点为根的(往下的)所有路径,也就是说统计的是以每个节点为「路径开头」的所有合法路径。


本题的一个优化切入点为「路径只能往下」,因此如果我们转换一下,统计以每个节点为「路径结尾」的合法数量的话,配合原本就是「从上往下」进行的数的遍历(最完整的路径必然是从原始根节点到当前节点的唯一路径),相当于只需要在完整路径中找到有多少个节点到当前节点的路径总和为 targetSumtargetSum


于是这个树上问题彻底转换一维问题:求解从原始起点(根节点)到当前节点 bb 的路径中,有多少节点 aa 满足 sum[a...b] = targetSumsum[a...b]=targetSum,由于从原始起点(根节点)到当前节点的路径唯一,因此这其实是一个「一维前缀和」问题。


具体的,我们可以在进行树的遍历时,记录下从原始根节点 rootroot 到当前节点 curcur 路径中,从 rootroot 到任意中间节点 xx 的路径总和,配合哈希表,快速找到满足以 curcur 为「路径结尾」的、使得路径总和为 targetSumtargetSum 的目标「路径起点」有多少个。


一些细节:由于我们只能统计往下的路径,但是树的遍历会同时搜索两个方向的子树。因此我们应当在搜索完以某个节点为根的左右子树之后,应当回溯地将路径总和从哈希表中删除,防止统计到跨越两个方向的路径。


代码:


class Solution {
    Map<Integer, Integer> map = new HashMap<>();
    int ans, t;
    public int pathSum(TreeNode root, int _t) {
        if (root == null) return 0;
        t = _t;
        map.put(0, 1);
        dfs(root, root.val);
        return ans;
    }
    void dfs(TreeNode root, int val) {
        if (map.containsKey(val - t)) ans += map.get(val - t);
        map.put(val, map.getOrDefault(val, 0) + 1);
        if (root.left != null) dfs(root.left, val + root.left.val);
        if (root.right != null) dfs(root.right, val + root.right.val);
        map.put(val, map.getOrDefault(val, 0) - 1);
    }
}
复制代码


  • 时间复杂度:O(n)O(n)
  • 空间复杂度:O(n)O(n)


最后



这是我们「刷穿 LeetCode」系列文章的第 No.437 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。


在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。


为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…


在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

相关文章
|
11月前
【LeetCode 35】112.路径总和
【LeetCode 35】112.路径总和
86 0
|
3月前
|
分布式计算 算法 Go
【LeetCode 热题100】BFS/DFS 实战:岛屿数量 & 腐烂的橘子(力扣200 / 994 )(Go语言版)
本文讲解了两道经典的图论问题:**岛屿数量(LeetCode 200)** 和 **腐烂的橘子(LeetCode 994)**,分别通过 DFS/BFS 实现。在“岛屿数量”中,利用深度或广度优先搜索遍历二维网格,标记连通陆地并计数;“腐烂的橘子”则采用多源 BFS,模拟腐烂传播过程,计算最短时间。两者均需掌握访问标记技巧,是学习网格搜索算法的绝佳实践。
133 1
|
4月前
|
Go 开发者 索引
【LeetCode 热题100】路径与祖先:二叉树中的深度追踪技巧(力扣33 / 81/ 153/154)(Go语言版)
本文深入探讨了LeetCode中四道关于「搜索旋转排序数组」的经典题目,涵盖了无重复和有重复元素的情况。通过二分查找的变形应用,文章详细解析了每道题的解题思路和Go语言实现代码。关键点包括判断有序区间、处理重复元素以及如何缩小搜索范围。文章还总结了各题的异同,并推荐了类似题目,帮助读者全面掌握二分查找在旋转数组中的应用。无论是初学者还是有经验的开发者,都能从中获得实用的解题技巧和代码实现方法。
249 14
|
3月前
|
Go
【LeetCode 热题100】BFS/DFS 实战:岛屿数量 & 腐烂的橘子(力扣200 / 994 )(Go语言版)
本篇博客详细解析了三道经典的动态规划问题:198. 打家劫舍(线性状态转移)、279. 完全平方数与322. 零钱兑换(完全背包问题)。通过 Go 语言实现,帮助读者掌握动态规划的核心思想及其实战技巧。从状态定义到转移方程,逐步剖析每道题的解法,并总结其异同点,助力解决更复杂的 DP 问题。适合初学者深入理解动态规划的应用场景和优化方法。
76 0
|
5月前
|
Go
【LeetCode 热题100】路径与祖先:二叉树中的深度追踪技巧(力扣437 / 236 )(Go语言版)
本文深入探讨二叉树中路径与祖先问题,涵盖两道经典题目:LeetCode 437(路径总和 III)和236(最近公共祖先)。对于路径总和 III,文章分析了双递归暴力解法与前缀和优化方法,后者通过哈希表记录路径和,将时间复杂度从O(n²)降至O(n)。在最近公共祖先问题中,采用后序遍历递归查找,利用“自底向上”的思路确定最近公共祖先节点。文中详细解析代码实现与核心要点,帮助读者掌握深度追踪技巧,理解树结构中路径与节点关系的本质。这类问题在面试中高频出现,掌握其解法意义重大。
110 4
|
5月前
|
算法 Go
【LeetCode 热题100】深入理解二叉树结构变化与路径特性(力扣104 / 226 / 114 / 543)(Go语言版)
本博客深入探讨二叉树的深度计算、结构变换与路径分析,涵盖四道经典题目:104(最大深度)、226(翻转二叉树)、114(展开为链表)和543(二叉树直径)。通过递归与遍历策略(前序、后序等),解析每题的核心思路与实现方法。结合代码示例(Go语言),帮助读者掌握二叉树相关算法的精髓。下一讲将聚焦二叉树构造问题,欢迎持续关注!
123 10
|
11月前
【LeetCode 36】113.路径总和II
【LeetCode 36】113.路径总和II
82 0
|
机器人 Python
【Leetcode刷题Python】62. 不同路径
LeetCode 62题 "不同路径" 的Python解决方案,使用动态规划算法计算机器人从网格左上角到右下角的所有可能路径数量。
188 0
|
存储 Python
【Leetcode刷题Python】64. 最小路径和
一种使用动态规划解决LeetCode上64题“最小路径和”的Python实现方法,通过维护一个一维数组来计算从网格左上角到右下角的最小路径总和。
70 1
【Leetcode刷题Python】64. 最小路径和
|
11月前
【LeetCode 34】257.二叉树的所有路径
【LeetCode 34】257.二叉树的所有路径
80 0