LeetCode 437路径总和III:前缀和与哈希表优化解法
1. 题目背景与核心问题LeetCode 437题路径总和 III是二叉树类问题中的经典题目主要考察对树结构的遍历和路径统计能力。题目要求给定一个二叉树的根节点和一个目标和找出路径和等于给定值的路径数量。这里的路径不需要从根节点开始也不需要在叶子节点结束但必须保证路径方向是向下的只能从父节点到子节点。这道题在面试中出现频率较高因为它能同时考察候选人对递归、深度优先搜索DFS以及前缀和等算法的掌握程度。我在第一次遇到这个问题时花了近两小时才找到最优解后来通过反复练习和总结终于形成了系统的解题思路。2. 基础解法双重递归分析2.1 暴力递归思路最直观的解法是使用双重递归外层递归遍历每个节点内层递归计算以当前节点为起点的所有路径和def pathSum(root, targetSum): if not root: return 0 def dfs(node, current_sum): if not node: return 0 current_sum node.val count 1 if current_sum targetSum else 0 return count dfs(node.left, current_sum) dfs(node.right, current_sum) return dfs(root, 0) pathSum(root.left, targetSum) pathSum(root.right, targetSum)这种解法的时间复杂度是O(n²)对于每个节点都要遍历其所有子节点。在树退化为链表的最坏情况下性能会急剧下降。2.2 复杂度优化思考暴力解法的主要问题在于存在大量重复计算。比如当我们计算完节点A的所有子路径后在计算节点A的父节点时又会重复计算A及其子节点的路径。这种重复计算导致了不必要的性能损耗。3. 最优解前缀和哈希表3.1 前缀和概念引入前缀和技巧通常用于数组问题但同样适用于树结构。核心思想是记录从根节点到当前节点的路径和然后通过哈希表快速查找是否存在满足条件的子路径。具体来说维护一个哈希表prefix_sum_count记录路径和出现的次数当前路径和curr_sum减去targetSum的值如果在哈希表中存在说明存在符合条件的子路径回溯时需要及时清理哈希表避免影响其他路径的计算3.2 代码实现与解析def pathSum(root, targetSum): from collections import defaultdict prefix_sum_count defaultdict(int) prefix_sum_count[0] 1 # 初始状态和为0出现1次 def dfs(node, curr_sum): if not node: return 0 curr_sum node.val # 查找是否有满足条件的子路径 res prefix_sum_count.get(curr_sum - targetSum, 0) # 更新当前路径和的出现次数 prefix_sum_count[curr_sum] 1 # 递归处理左右子树 res dfs(node.left, curr_sum) res dfs(node.right, curr_sum) # 回溯恢复状态 prefix_sum_count[curr_sum] - 1 return res return dfs(root, 0)这个算法的时间复杂度降到了O(n)因为每个节点只需要访问一次。空间复杂度主要取决于哈希表的大小和递归栈的深度最坏情况下也是O(n)。4. 关键细节与边界条件4.1 哈希表初始化prefix_sum_count[0] 1这一初始化非常重要。它表示在路径开始前和为0的路径已经出现过1次。这样当从根节点开始的路径和正好等于targetSum时我们就能正确计数。4.2 回溯处理在递归返回前必须执行prefix_sum_count[curr_sum] - 1这是为了保证在计算不同分支时不会互相干扰。比如左子树计算完成后在计算右子树时左子树的路径和不应该被计入。4.3 数值范围考虑题目没有限制节点值的范围如果节点值很大curr_sum可能会溢出。但在Python中整数不会溢出所以不需要特别处理。如果用其他语言实现可能需要考虑使用长整型。5. 测试用例设计5.1 基础测试用例# 用例1简单二叉树 # 10 # / \ # 5 -3 # / \ \ # 3 2 11 # 目标值8预期结果35.2 特殊边界用例# 用例2空树 # 目标值任意预期结果0 # 用例3单节点树 # 节点值5目标值5预期结果1 # 用例4所有节点值相同 # 2 # / \ # 2 2 # 目标值2预期结果35.3 复杂用例# 用例5路径和可能多次出现 # 5 # / \ # 4 8 # / / \ # 11 13 4 # / \ / \ # 7 2 5 1 # 目标值22预期结果36. 常见错误与调试技巧6.1 忘记回溯这是最常见的错误之一。如果在递归返回前没有及时清理哈希表中的记录会导致后续计算错误。可以通过打印哈希表内容来验证回溯是否正确。6.2 初始化错误忘记初始化prefix_sum_count[0] 1会导致漏计从根节点开始的满足条件的路径。可以通过测试单节点树来验证。6.3 路径方向混淆题目要求路径必须是向下的有些解法可能会错误地统计了从子节点到父节点的路径。可以通过构造不对称的树来验证。7. 算法扩展与变种7.1 输出所有路径如果题目要求输出所有满足条件的路径而不仅仅是计数我们可以修改算法用哈希表记录路径和对应的节点列表然后在找到满足条件的路径时输出对应的节点序列。7.2 路径必须从根到叶这是路径总和问题的经典版本LeetCode 112题。相比本题这个变种更简单因为路径的起点和终点都固定了。7.3 路径方向不限如果取消路径必须是向下这一限制问题会变得更加复杂可能需要将树转换为图来处理。这种情况在实际面试中较少出现。8. 个人刷题心得这道题我前后做了5遍才完全掌握。最大的收获是理解了前缀和技巧不仅适用于数组也可以灵活应用于树结构。在实际编码时有几点特别需要注意画图辅助理解对于树问题画出具体的树结构并手动计算路径和能帮助理解算法的执行过程。分步验证先实现暴力解法确保理解题意再逐步优化到前缀和解法这样能更好地理解优化点在哪里。边界测试特别注意空树、单节点树、所有节点值相同等特殊情况这些往往是算法出问题的地方。回溯清理这是最容易出错的地方可以在递归进入和返回时打印当前状态帮助定位问题。经过多次练习后我现在能在15分钟内写出无bug的前缀和解法。这道题的解题思路也帮助我解决了其他类似的问题比如和为K的子数组等。