LeetCode 112. 路径总和

2 阅读4分钟

LeetCode 112. 路径总和

摘要

LeetCode 112 路径总和题解,使用前序 DFS 递归,通过递减目标值判断路径是否存在,利用逻辑或实现快速短路返回,时间复杂度 O(n),空间复杂度 O(n)(递归栈)。

标签

#算法 #LeetCode #二叉树 #DFS #递归 #题解

目录

  1. 题目描述

  2. 易错点

  3. 思路

    • 为什么使用前序 DFS
    • 减法思想的优雅之处
    • 逻辑或的快速回调
  4. 编写代码


1. 题目描述

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

叶子节点 是指没有子节点的节点。

示例 1:

text

输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
解释:等于目标和的根节点到叶节点路径为 5->4->11->2

示例 2:

text

输入:root = [1,2,3], targetSum = 5
输出:false
解释:不存在根节点到叶子节点的路径和为 5。

示例 3:

text

输入:root = [], targetSum = 0
输出:false

提示:

  • 树中节点的数目在范围 [0, 5000] 内
  • -1000 <= Node.val <= 1000
  • -1000 <= targetSum <= 1000

2. 易错点

  1. 空树直接返回 false(即使 targetSum 为 0)。
  2. 路径必须是从根节点到叶子节点,不能提前在非叶子节点结束。
  3. 判断条件应为:当前节点是叶子且节点值等于剩余目标值。
  4. 递归时,将 targetSum 减去当前节点值传递给子节点,而不是累加路径和。
  5. 逻辑或的短路特性:一旦左子树返回 true,右子树将不再执行,需确保这种快速返回不会影响结果(因为只需存在一条路径)。

3. 思路

为什么使用前序 DFS

本题需要从根节点出发,沿着路径向下累加(或递减)直到叶子。处理顺序必然是:先处理当前节点,再递归处理其子节点。这正是前序遍历(根-左-右)的特征。

若使用中序或后序,则需要额外保存路径状态,或难以在遍历到叶子时判断是否满足条件。前序 DFS 天然适配:每到一个节点,立即将当前节点值纳入路径计算,然后向下传递,符合人类思维习惯。

递归结构直接对应前序:先判断当前节点,再递归左、右子树。代码简洁且易理解。

减法思想的优雅之处

常见的思路是维护一个当前路径和 sum,每次累加节点值,到叶子时比较 sum == targetSum。这需要额外传递累计值。

减法思想将目标值作为“剩余需要补齐的和”向下传递:

  • 从根开始,目标为 targetSum
  • 进入子节点时,目标变为 targetSum - node.val
  • 到叶子节点时,只需检查 node.val == remaining,等价于路径和等于原始目标。

这种做法的优点:

  • 无需额外维护累计变量,减少参数个数。
  • 直观反映“还差多少”的含义,更贴近问题本质。
  • 递归终止条件清晰:叶子节点时只需一次比较。

逻辑或的快速回调

代码中 return dfs(left, ...) || dfs(right, ...) 利用了 C 语言逻辑或的短路求值特性:

  • 先计算左子树递归,若返回 true,则整个表达式立即为 true,右子树递归根本不会执行。
  • 若左子树返回 false,才继续计算右子树。

这带来的好处:

  • 一旦找到一条有效路径,立即停止搜索,避免不必要的遍历,提升效率。
  • 代码简洁,一行完成两个子树的探索与结果合并。

注意:短路求值仅在操作数为布尔类型时生效,这里正是 bool 类型,完全适用。

时间复杂度:最坏情况下遍历所有节点 O(n),但短路特性可能提前终止,平均更快。空间复杂度:递归栈深度 O(h)。


4. 编写代码

c 运行

c

bool hasPathSum(struct TreeNode* root, int targetSum) {
    if(!root) return false;
    if(!root->right && !root->left && root->val == targetSum)
        return true;
    return hasPathSum(root->left, targetSum - root->val) || hasPathSum(root->right, targetSum - root->val);
}