112. 路径总和(Path Sum)

难度:简单 | 主题:二叉树、DFS

题目

给你二叉树的根节点和一个整数 targetSum,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值之和等于 targetSum

示例

 
输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
 
        5
 
       / \
 
      4   8
 
     /   / \
 
    11  13  4
 
   /  \
 
  7    2
 
输出:true(路径 5→4→11→2 = 22)
 

思路

先讲个故事:探险家的燃料

你是一个探险家,带着精确的 22 升燃料从营地出发(根节点),要到宝藏埋藏点(叶子节点)。

每经过一个区域,必须留下一些燃料作为过路费(节点值)。你需要判断是否存在一条从营地到宝藏的路线,刚好把燃料用完。

关键规则

  • 只能在叶子节点(没有岔路的地方)结束旅程

  • 到了叶子,燃料必须刚好用完

  • 不能折返,只能从根往叶子走


引导式推导:从手算到递归

第 1 步:从根出发

 
targetSum = 22
 
root = 5
 
剩余 = 22 - 5 = 17
 

第 2 步:选择方向

到 4 还是 8?

 
走左 → 4,剩余 17 - 4 = 13
 
走右 → 8,剩余 17 - 8 = 9
 

第 3 步:继续往下

 
走 4 → 到 11,剩余 13 - 11 = 2
 
     → 到 7(叶子),剩余 2 - 7 = -5 ❌ 超了
 
     → 到 2(叶子),剩余 2 - 2 = 0 ✅ 找到!
 

第 4 步:归纳发现

从根到叶子的路径和 = target ⇔ 从根走到某个叶子,沿途减掉所有节点值后刚好等于 0。

 
hasPathSum(node, target):
 
    if 是叶子: return target == node.val
 
    return hasPathSum(node.left, target - node.val) or
 
           hasPathSum(node.right, target - node.val)
 

为什么用减法代替加法?

减法方案更简洁:不需要额外的变量记录当前和,target 一路减下去就行。

思路做法评价
累加传当前和,到叶子比较需要多传一个参数
减法传剩余目标,到叶子看是否为 0参数少,终止条件直观

flowchart TD

    root["root=5<br/>target=22"]

    root --> left4["左: 4<br/>target=17"]

    root --> right8["右: 8<br/>target=14"]

    left4 --> n11["11<br/>target=13"]

    n11 --> leaf7["7(叶)<br/>target=6 ❌"]

    n11 --> leaf2["2(叶)<br/>target=0 ✅"]

    right8 --> n13["13(叶)<br/>target=1 ❌"]

    right8 --> n4["4<br/>target=6"]

    n4 --> null1["null ❌"]

    n4 --> leaf1["1(叶)<br/>target=5 ❌"]

    style leaf2 fill:#9cf,stroke:#333


代码

 
def hasPathSum(self, root, targetSum):
 
    if not root:
 
        return False
 
    # 叶子:判断剩余值是否等于当前节点值
 
    if not root.left and not root.right:
 
        return targetSum == root.val
 
    # 非叶子:递归左右子树,目标值减去当前节点值
 
    return (self.hasPathSum(root.left, targetSum - root.val) or
 
            self.hasPathSum(root.right, targetSum - root.val))
 

注意self.hasPathSum(root.left, targetSum - root.val) 这一句中,减法在函数调用的参数中完成,当前层的 targetSum 不会被修改。这就是”减法 + 递归”的巧妙之处——不需要显式回溯。


复杂度

指标解释
时间O(n)最坏遍历所有节点
空间O(h)递归栈深度,最坏 O(n)

实战考量

频率分析

出现在:实践中作为二叉树递归入门题,常见于一面热身。如果回答顺畅,通常会进一步追问 113(输出路径)或 437(任意路径)。

延伸思考

Q:如果要求返回路径本身,怎么做?

A:用 path 列表记录遍历路径,到叶子时如果和为 target 就加入结果集(113路径总和II)。需要回溯:递归前 push,递归后 pop。

Q:回溯和这里的减法有什么区别?

A:本题直接用函数参数递归,每层有独立变量拷贝,天然”回溯”。113 题要记录路径,用全局变量必须显式回溯(撤销选择)。

Q:如果不要求根到叶子,而是任意起点和终点呢?

A:437路径总和III,前缀和法 + 哈希表。O(n) 时间,思路基于两数之差。

Q:BFS 能做吗?

A:能。用队列存 (node, remaining) 进行层序遍历,到叶子时检查 remaining 是否为 0。空间 O(n),不如 DFS 简洁。

Q:节点值可以是负数吗?

A:可以。有负数时不能提前剪枝(即使当前剩余 < 0,后面加负数还可能回到 0),必须遍历所有路径。

易错点

  • 叶子定义:not root.left and not root.right,不是 root is None

  • 空树(root = None)返回 False 而不是 True

  • 路径必须从根到叶子,不能停在中间节点

  • 有负数时不能提前剪枝


生活类比

路径总和 → 登山补给

你从大本营(根)出发,带固定量的补给。

路上每个营地都会消耗一些补给。

你只能在**山顶(叶子)**插旗,如果到达山顶时补给刚好用完,挑战成功。

不用回头,一路向下。这就是 DFS + 减法


相关题目

题目关系
113路径总和II输出具体路径(需要回溯)
437路径总和III不要求根到叶,任意路径,前缀和
112路径总和本题

→ 返回题单:LeetCode学习路线图 > 五、二叉树

速记卡(面试闪卡)

Q1:一句话讲清「112. 路径总和(Path Sum)」到底是什么?

A:判断二叉树中是否存在根到叶子的路径,其节点值之和等于 targetSum。

Q2:思路 —— 怎么理解?

A:像探险家带固定燃料从营地到宝藏(叶子),每过一区留过路费(节点值),到叶子燃料刚好用完即成功。用减法:target 一路减节点值,到叶子看是否为 0,比加法少传参。

Q3:代码 —— 怎么理解?

A:空树返 False;叶子(无左右孩子)返 targetSum==node.val;否则递归左右、目标减当前值。减法在参数里完成,当前层 target 不被改,天然无需显式回溯。

Q4:复杂度 —— 怎么理解?

A:时间 O(n) 最坏遍历所有节点;空间 O(h) 递归栈深度,最坏 O(n)(链状树)。

Q5:实战考量 —— 怎么理解?

A:二叉树递归入门,一面热身,常追问 113(输出路径需回溯)或 437(任意路径前缀和)。有负数不能提前剪枝;空树返 False;路径必须根到叶子。

Q6:核心速记主线有哪些?

  • DFS 从根到叶子,target 一路减节点值,叶子处看是否归零

  • 空树返 False,叶子定义=无左右孩子

  • 时间 O(n) 空间 O(h),减法递归天然免回溯

  • 有负数不剪枝;常追问 113/437

口诀

A:路径总和判根叶,燃料一路减节点

到叶归零即成功,减法递归免回溯

空树 False 叶无子,时间 O(n) 栈 O(h)

负数不剪莫提前,追问一一三与四三七

相关链接