Leetcode 二叉树 6

题号直达: 437 路径总和 III

LeetCode 437. 路径总和 III ↑

题目

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

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

提示

  • 二叉树的结点数在范围 [0, 1000] 内
  • -10^9 ≤ Node.val ≤ 10^9
  • -1000 ≤ targetSum ≤ 1000

示例图

路径总和 III 示例

代码(Python)解法一:BFS 枚举起点 + DFS 统计路径

对每个结点作为路径起点,向下 DFS,用「剩余目标和」targetSum - p.val 递归;当前结点若恰好等于剩余目标和则计数 +1。

from collections import deque

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right

class Solution(object):
    def __init__(self):
        self.num = 0

    def rootSum(self, p, targetSum):
        if not p:
            return 0

        count = 1 if p.val == targetSum else 0
        count += self.rootSum(p.left, targetSum - p.val)
        count += self.rootSum(p.right, targetSum - p.val)
        return count

    def pathSum(self, root, targetSum):
        if not root:
            return 0

        temp = deque()
        temp.append(root)
        self.num = 0

        while temp:
            p = temp.popleft()
            self.num += self.rootSum(p, targetSum)

            if p.left:
                temp.append(p.left)
            if p.right:
                temp.append(p.right)
        return self.num

思路解析(解法一)

  • 外层 BFS:队列遍历每一个结点,将其视为路径起点,避免漏数「不以根为起点」的路径。
  • 内层 rootSum(p, targetSum):从结点 p 出发,只向下走;若 p.val == targetSum 则当前路径算一条;再在左右子树中继续找和为 targetSum - p.val 的路径条数并累加。
  • self.num:对每个起点累加 rootSum 的返回值;每次 pathSum 入口将 self.num 置 0,避免多次调用时残留。
  • 复杂度:每个起点做一次 DFS,最坏 O(n) 层递归深度,整体最坏约 O(n²),在 n ≤ 1000 下可接受;存在 前缀和 + 哈希 的 O(n) 做法,可作进阶。

代码(Python)解法二:前缀和 + 哈希表(一次 DFS,O(n))

说明(个人备注):第二种方法(前缀和 + 哈希)尚未理解透彻,先存档代码与注释;日后可对照题解或视频再啃。

从根到当前结点有一条自上而下的路径,记路径和为 curr。
若某段路径和为 targetSum,等价于存在祖先侧前缀和 p,使 curr - p = targetSum,即查 prefix[curr - targetSum]。
哈希表记录当前 DFS 链上各前缀和出现次数;入子树 +1,回溯 -1。

from collections import defaultdict

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right

class Solution(object):
    def pathSum(self, root, targetSum):
        # prefix[s]:当前 DFS 链上,前缀和 s 出现次数
        prefix = defaultdict(int)
        # 前缀和 0 计 1 次:整段路径和恰为 targetSum 时
        # 对应「减去空前缀」的一种情况
        prefix[0] = 1

        def dfs(root, curr):
            # root:当前子树根
            # curr:进入本结点前,从根到父结点的路径和
            if not root:
                return 0
            ret = 0
            # 加上本结点值,得到根到当前结点的路径和
            curr = curr + root.val
            # 以当前结点为终点的合法路径数:
            # 已有多少前缀和为 curr - targetSum
            ret = ret + prefix[curr - targetSum]
            # 当前结点作为前缀终点,curr 计数 +1,供子树用
            prefix[curr] += 1
            # 左子树
            ret += dfs(root.left, curr)
            # 右子树
            ret += dfs(root.right, curr)
            # 回溯:离开本结点,撤销 prefix[curr]
            # 避免兄弟子树误用本分支计数
            prefix[curr] -= 1
            return ret

        return dfs(root, 0)

思路解析(解法二)

  • 核心等式:根到 v 的路径和为 curr(v)。若从某祖先下一结点到 v 的和为 targetSum,则 curr(v) - curr(u) = targetSum,即 curr(u) = curr(v) - targetSum。
    用 prefix[curr - targetSum] 统计路径上此前出现过几次该前缀和。
  • prefix[0] = 1:当 curr == targetSum 时,需要 prefix[0] 为 1,对应从根到当前整段一条路径。
  • 回溯 prefix[curr] -= 1:返回父层前撤销当前结点对哈希的贡献,否则兄弟子树会串台。
  • 与解法一:每个结点只访问一次,时间 O(n),哈希 O(n)。
posted @ 2026-10-12 00:06  Phier  阅读(2)  评论(0)    收藏  举报