LeetCode 112. 路径总和
本节目标
沿根到叶路径递减剩余目标值,并只在叶节点判断是否恰好归零。
这是路径状态与结构变换的第一道母题。它要求判断是否存在一条从根节点走到叶节点的路径,使节点值之和等于目标值。
题意与关键边界
路径必须从根开始、在叶节点结束。若当前节点已经使和达到目标,但它还有孩子,路径还没有结束,不能返回成功。例如树 [5, 1] 的目标为 5 时,根节点不是叶子,答案是 false。
朴素思路与瓶颈
可以先枚举每一条根到叶路径,再分别累加判断。这样需要额外保存完整路径,并在每个叶子重复计算。题目只需要真假,不需要输出路径,因此无需保存路径容器。
剩余目标不变量
定义 dfs(node, remaining):进入 node 前,remaining 是从当前节点到某个叶子还必须凑出的和。进入节点后先减去 node.val;若它是叶子,恰好减为零时成功;否则把更新后的值继续传给左右子树。
整数按值传递,左右递归不会共享可变状态,也就不需要回溯。空节点不可能补齐一条路径,直接返回 false。
正确性依据
对任意调用 dfs(node, remaining),扣除当前值后,目标路径剩余部分只能完全位于左子树或右子树。叶节点是唯一允许结束路径的位置,因此叶子处返回 remaining == 0 恰好对应一条合法根到叶路径;非叶节点继续递归不会把中途命中误计为答案。左右子树任一成功时,当前子树就存在合法路径;都失败时不存在。
代码实现
- C++
- Python
class Solution {
private:
bool dfs(TreeNode* node, int remaining) {
if (node == nullptr) {
return false;
}
remaining -= node->val;
if (node->left == nullptr && node->right == nullptr) {
return remaining == 0;
}
return dfs(node->left, remaining) || dfs(node->right, remaining);
}
public:
bool hasPathSum(TreeNode* root, int targetSum) {
return dfs(root, targetSum);
}
};
from typing import Optional
class Solution:
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
def dfs(node: Optional[TreeNode], remaining: int) -> bool:
if node is None:
return False
remaining -= node.val
if node.left is None and node.right is None:
return remaining == 0
return dfs(node.left, remaining) or dfs(node.right, remaining)
return dfs(root, targetSum)
两份源码都把递归职责命名为 dfs,并只在左右指针都为空时判断剩余目标。它们不读取输入流,树节点由平台提供。
复杂度分析
最坏情况下访问每个节点一次,时间复杂度为 O(n)。递归调用栈深度为树高 h,额外空间为 O(h);退化树时为 O(n),平衡树时为 O(log n)。
边界与易错点
- 空树没有根到叶路径,返回
false。 - 单节点树只在节点值等于目标时返回
true。 - 负数不改变算法:剩余目标继续相减即可。
- 不能把“当前累计和已经等于目标”当作成功条件,必须再确认当前节点是叶子。
模式迁移
若要返回所有满足条件的根到叶路径,就额外维护一个共享 path,进入节点加入、返回时删除。若路径可以从任意节点开始,则不能只传递根路径的剩余目标,需要转向树上前缀和模型。