力扣刷题#30-0112-路径总和
力扣刷题#30-0112-路径总和
题目
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于 targetSum。存在返回 true,否则返回 false。
叶子节点是指没有子节点的节点。
输入: root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出: true
解释: 5 → 4 → 11 → 2 这条路径和为 22
我的思路
直觉:DFS + 回溯
看到"根到叶子的一条路径",直觉就是 DFS——一条路走到底,不对就回溯换一条。
DFS 其实就是递归的变种——递归函数自己调用自己,天然就是"先深入、后返回"的结构。树的前序/中序/后序、翻转、深度、对称,全是这个套路。
关键设计:不是累加,是递减
与其记录"走过的路径和"再和 targetSum 比,不如把 targetSum 一路减下去:
到达每个节点:targetSum -= node->val
到达叶子节点:if (targetSum == 0) → 找到路径!
好处:不用额外变量存路径和,递归参数本身就携带"还差多少"。
生活化类比:
你有一张 22 元的购物卡,每经过一个商店就刷掉店里的价格。走到最后一家店(叶子)时卡刚好归零——这条路就对了。刷完还有余额或透支,就回头换条路。
我的 AC 代码
class Solution {
public:
bool hasPathSum(TreeNode* root, int targetSum) {
if (root == nullptr)
return false;
if (!root->left && !root->right)
return targetSum == root->val;
return hasPathSum(root->left, targetSum - root->val) ||
hasPathSum(root->right, targetSum - root->val);
}
};
代码逐段解析
第 3-4 行:空节点
if (root == nullptr) return false;
没有节点就没有路径,返回 false。这是递归的终止条件之一。
第 5-6 行:叶子判断
if (!root->left && !root->right)
return targetSum == root->val;
叶子 = 没有左孩子且没有右孩子。到了叶子,检查"卡里剩下的钱"是否正好等于当前商店价格。
第 8-9 行:递归下降
return hasPathSum(root->left, targetSum - root->val) ||
hasPathSum(root->right, targetSum - root->val);
不是叶子 → 从 targetSum 里扣掉当前节点值,继续往左右走。|| 表示"左边找到路径或者右边找到路径"。
回溯的精髓:递归参数 targetSum - root->val 是按值传递的。递归返回后,上一层的 targetSum 保持原样——自动回到进入子树之前的状态,不需要手动恢复。
为什么 || 就是回溯?
hasPathSum(root->left, ...) 返回后,如果左边没找到(false),|| 会继续算右边的 hasPathSum(root->right, ...)——这就是"走不通换条路"。两个递归调用共享同一层剩余的 targetSum - root->val,互不干扰。
复杂度分析
| 维度 | 值 | 说明 |
|---|---|---|
| 时间复杂度 | O(n) | 最坏遍历所有节点 |
| 空间复杂度 | O(h) | 递归栈深度 = 树高 h |
关键点总结
| 关键点 | 说明 |
|---|---|
| 核心设计 | 递减法:targetSum - node->val 一路传下去 |
| 叶子判断 | !root->left && !root->right,中间节点不能提前判断 |
| 回溯实现 | 按值传递参数,递归返回自动恢复状态 |
| DFS = 递归变种 | 递归调用天然"先深入后返回",树的题都是这个套路 |
本文档由 AI 辅助生成,作者提供问题,思路和代码,AI仅负责文本修饰,综合获得以上内容。

浙公网安备 33010602011771号