2026-10-31 18:14:14
首先,我们需要清楚地理解题目要求。给定一个二叉树,每个节点都有一个整数值。我们需要找出所有路径和等于给定数值的路径总数。这里的路径有以下几个特点:
给定的示例是:
root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8 10 / 5 -3 / 3 2 11 / 3 -2 1需要找出所有和为8的路径。根据示例,正确的路径有:
让我们验证一下:
确实有三条路径满足条件。
解题思路思路一:暴力法暴力法的思路是:
具体步骤:
代码实现:
class Solution: def pathSum(self, root: TreeNode, _sum: int) -> int: res = 0 def dfs1(root, _sum): nonlocal res if not root: return if _sum - root.val == 0: res += 1 _sum -= root.val dfs1(root.left, _sum) dfs1(root.right, _sum) def dfs2(root): if not root: return dfs1(root, _sum) dfs2(root.left) dfs2(root.right) dfs2(root) return res解释:
复杂度分析:
这种方法利用了前缀和的概念,类似于数组中的前缀和,但在树中进行。前缀和可以帮助我们快速计算从某个节点到当前节点的路径和。
前缀和定义:
从根节点到当前节点的路径上所有节点的和。
思路:
计算当前的前缀和cur。
计算target = cur - _sum,即我们需要的前缀和,使得从某个祖先节点到当前节点的路径和为_sum。
res += prefix[target],即之前有多少前缀和等于target,就有多少条路径满足条件。
更新prefix[cur],表示当前前缀和的出现次数加一。
递归处理左子树和右子树。
回溯:在返回之前,将prefix[cur]减一,因为我们要回到上一层,当前的前缀和不再影响其他分支。
代码实现:
class Solution: def pathSum(self, root: TreeNode, _sum: int) -> int: from collections import defaultdict prefix = defaultdict(int) prefix[0] = 1 res = 0 def dfs(root, cur): nonlocal res if not root: return cur += root.val target = cur - _sum res += prefix[target] prefix[cur] += 1 dfs(root.left, cur) dfs(root.right, cur) prefix[cur] -= 1 dfs(root, 0) return res解释:
cur:从根节点到当前节点的路径和。
target = cur - _sum:如果之前存在前缀和等于target,那么从那个节点到当前节点的路径和就是_sum。
res += prefix[target]:累加满足条件的路径数量。
prefix[cur] += 1:记录当前前缀和。
递归处理左右子树。
prefix[cur] -= 1:回溯,恢复prefix的状态。
复杂度分析:
暴力法:
优点:直观,易于理解。
缺点:时间复杂度较高,对于大树效率较低。
前缀和 + 回溯:
优点:时间复杂度低,效率高。
缺点:需要理解前缀和的概念,实现稍复杂。
在实际应用中,如果树的规模不大(如题目中节点数不超过1000),暴力法也是可以接受的。但对于更大的树或需要高效处理的场景,前缀和的方法更为优越。
验证示例让我们用前缀和的方法验证示例:
初始:
遍历:
根节点10:
cur = 0 + 10 = 10
target = 10 - 8 = 2
prefix[2]不存在,res += 0
prefix = {0:1, 10:1}
递归左(5),右(-3)
左子节点5:
cur = 10 + 5 = 15
target = 15 - 8 = 7
prefix[7]不存在,res += 0
prefix = {0:1, 10:1, 15:1}
递归左(3),右(2)
左子节点3:
cur = 15 + 3 = 18
target = 18 - 8 = 10
prefix[10] = 1,res += 1(路径10->5->3,但实际是从5开始,所以这里需要调整理解)
实际上,前缀和是从根开始的,所以target=10对应的是从根到某个点的和为10,然后从那个点到当前点的和为8。但这里我们是从5开始,所以可能需要重新思考。
看起来这里的前缀和定义可能需要调整。更准确地说,前缀和是从当前节点到根的路径和的相反数(因为我们在计算cur时是从根累加下来的)。可能需要重新梳理。
更准确的前缀和定义:
我们定义prefix为从根到当前节点的路径和。
当我们计算cur时,是从根到当前节点的和。
我们需要找的是是否存在一个祖先节点,使得cur - ancestor_sum = _sum,即ancestor_sum = cur - _sum。
因此,prefix应该记录从根到当前节点的路径和。
可能在之前的解释中有些混淆。让我们重新定义:
prefix记录从根到当前节点的路径和的出现次数。
初始时,prefix = {0:1},表示在根节点之前,前缀和为0(即没有节点时)。
对于每个节点,计算从根到该节点的和cur。
我们需要找的是prefix[cur - _sum],即之前有多少路径的和为cur - _sum,这样从那个节点到当前节点的和就是_sum。
然后将cur加入prefix,递归处理子节点,最后回溯(prefix[cur] -= 1)。
这样,对于示例:
初始prefix = {0:1}
根10:
cur = 10
target = 10 - 8 = 2
prefix[2]不存在,res += 0
prefix = {0:1, 10:1}
递归左5,右-3
左5:
cur = 10 + 5 = 15
target = 15 - 8 = 7
prefix[7]不存在,res += 0
prefix = {0:1, 10:1, 15:1}
递归左3,右2
左3:
cur = 15 + 3 = 18
target = 18 - 8 = 10
prefix[10] = 1,res += 1(路径5->3)
prefix = {0:1, 10:1, 15:1, 18:1}
递归左3,右-2
左3:
cur = 18 + 3 = 21
target = 21 - 8 = 13
prefix[13]不存在,res += 0
prefix = {0:1, 10:1, 15:1, 18:1, 21:1}
无左右子节点,返回
prefix[21] -= 1(即删除,因为之前是1,减后为0)
右-2:
cur = 18 + (-2) = 16
target = 16 - 8 = 8
prefix[8]不存在,res += 0
prefix = {0:1, 10:1, 15:1, 18:1, 16:1}
无左右子节点,返回
prefix[16] -= 1
返回到5的右2:
cur = 15 + 2 = 17
target = 17 - 8 = 9
prefix[9]不存在,res += 0
prefix = {0:1, 10:1, 15:1, 18:1, 17:1}
递归右1
右1:
cur = 17 + 1 = 18
target = 18 - 8 = 10
prefix[10] = 1,res += 1(路径5->2->1)
prefix = {0:1, 10:1, 15:1, 18:2}(因为之前18已经存在)
无左右子节点,返回
prefix[18] -= 1(从2减到1)
返回到10的右-3:
cur = 10 + (-3) = 7
target = 7 - 8 = -1
prefix[-1]不存在,res += 0
prefix = {0:1, 10:1, 15:1, 18:1, 7:1}
递归右11
右11:
cur = 7 + 11 = 18
target = 18 - 8 = 10
prefix[10] = 1,res += 1(路径-3->11)
prefix = {0:1, 10:1, 15:1, 18:2, 7:1}
无左右子节点,返回
prefix[18] -= 1(从2减到1)
最终res = 3,与示例一致。
在实现前缀和的方法时,容易犯的错误包括:
在Python中,可以使用defaultdict来简化哈希表的操作:
from collections import defaultdictclass Solution: def pathSum(self, root: TreeNode, _sum: int) -> int: prefix = defaultdict(int) prefix[0] = 1 res = 0 def dfs(root, cur): nonlocal res if not root: return cur += root.val res += prefix[cur - _sum] prefix[cur] += 1 dfs(root.left, cur) dfs(root.right, cur) prefix[cur] -= 1 dfs(root, 0) return res总结通过这道题目,我们学习了两种解决路径和问题的方法:
在实际应用中,应根据数据规模选择合适的方法。对于LeetCode的这道题目,两种方法都可以通过,但前缀和的方法更为高效。