Leetcode437 路径总和 III 双递归与前缀和
最简单的思路为双递归,内部递归函数用于计算以 node 节点为头元素的路径和, 外部递归函数用于遍历所有节点。
即遍历以每个节点为头元素的所有符合条件的路径:
/** * @Author Niuxy * @Date 2020/7/15 11:04 下午 * @Description 双递归 */ public int pathSum(TreeNode root, int sum) { if (root == null) { return 0; } return pathSum0(root, sum, 0) + pathSum(root.right, sum) + pathSum(root.left, sum); } public int pathSum0(TreeNode node, int sum, int preSum) { if (node == null) { return 0; } int currentSum = preSum + node.val; int re = pathSum0(node.left, sum, currentSum) + pathSum0(node.right, sum, currentSum); if (sum == currentSum) { re++; } return re; }
计算过程存在大量重复求和计算。比如 [1,2,3,4,5] ,1-5 的路径和为 2-5 的路径和加 1 ,而在计算过程中,2-5 的路径和被计算了两次。
内部函数建立缓存只能由 node,currentSum 两个坐标为依据,但这两个坐标命中的结果,整个计算过程只被使用过一次。也就是说内部函数的定义无法帮我们找出上述的重复计算情况。
参考数组求区间和时,可以使用双指针复用局部的区间和。
也可以在遍历一条路径时,通过区间和的差来复用该路径上的区间和。
从头元素到节点 i 的区间和为 I ,到节点 j 的区间和为 J 。则节点 i 到 j 的区间和为 J-I 。其中 I 与 J 即为节点 I 与 节点 J 的前缀和。
在遍历一条路径时,记录下每一个节点的前缀和,通过前缀和的差值寻找区间和为某值的路径是否存在。
因为要寻找路径的总和是确定的 sum ,某节点的前缀和 currentSum 是确定的。寻找的区间为 currentSum-x = sum , 也就是遍历每个节点时,寻找其前面前缀和为 x = currentSum-sum
的节点的数量。因为 x 是确定的,在一条路径上嗅探时,可以用哈希表存储前面节点的前缀和,以 O(1) 时间复杂度寻找前缀和为 x 的节点。
语义为遍历每个节点时,寻找该路径上,所有以该节点为尾结点的符合题意的区间。
目标区间的尾结点必为该路径上某一节点,该方式可完整遍历解空间,并帮助我们避免重复的求和计算。
/** * @Author Niuxy * @Date 2020/7/15 11:52 下午 * @Description 前缀和解法 */ public final int pathSum1(TreeNode root, int sum) { return pathSum1(root, sum, 0, new HashMap<Integer, Integer>()); } public final int pathSum1(TreeNode root, int sum, int preSum, Map<Integer, Integer> cache) { if (root == null) { return 0; } int currentRe = 0; int currentSum = preSum + root.val; if (currentSum == sum) { currentRe++; } //以该节点为末尾节点,向上寻找满足条件的路径数: currentRe - x = sum -> x = currentSum - sum currentRe += cache.getOrDefault(currentSum - sum, 0); //本节点前缀和 cache.put(currentSum, cache.getOrDefault(currentSum, 0) + 1); currentRe += pathSum1(root.left, sum, currentSum, cache) + pathSum1(root.right, sum, currentSum, cache); cache.put(currentSum, cache.get(currentSum) - 1); return currentRe; }
与数组双指针遍历区间和的思路类似,前缀和也是避免重复计算的一种思路。
同时,在“以某个节点为头节点”进行遍历时无法避免的重复计算,转变为“以某个元素为尾结点”进行遍历便解决了。
因为“以某个节点为头节点”时,已计算的结果不足以支撑计算出当前结果。“以某个元素为尾结点”则是在根节点与尾结点间寻找子路径,以计算出的前缀和结果足以支撑计算出当前结果,以此可以避免重复计算。
在很多情况下,逆向思维是破局的不二法门。但局内逻辑繁多,确定可以在哪个逻辑上尝试逆向,需要大量的练习。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构