给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。
路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
示例 1:

输入:root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
输出:3
解释:和等于 8 的路径有 3 条,如图所示。
示例 2:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:3
提示:
- 二叉树的节点个数的范围是
[0,1000] -109 <= Node.val <= 109-1000 <= targetSum <= 1000
1. 算法核心思想
本题要求寻找二叉树中节点值之和等于 targetSum 的路径数量。路径方向必须向下,但起点和终点可以是任意节点。为了优化时间与空间复杂度,本解法采用了前缀和与回溯的思想。
- 前缀和(Prefix Sum): 在树的遍历过程中,记录从根节点到当前节点的路径总和。若要求某一段子路径的节点和等于
targetSum,等价于寻找在当前路径上,是否存在一个历史前缀和,使得当前节点前缀和 - 历史前缀和 = targetSum。 - 传引用与回溯(Backtracking): 为了避免在每次递归调用时复制数组(导致空间和时间复杂度大幅增加),全局使用唯一的
vector并通过引用(&)传递。 由于左右子树的路径是相互独立的,当一条分支遍历结束,准备返回上一层时,必须将当前节点产生的前缀和从数组中移除(即状态还原/回溯),以确保不影响其他分支的计算。
2. 代码逻辑拆解
-
主函数
pathSum:- 初始化计数器
count为 0。 - 声明存储前缀和的数组
arr。 - 关键初始化:在数组中预先压入
0。这是为了处理“从根节点开始的路径刚好等于targetSum”的情况(即当前前缀和 - 0 = targetSum)。
- 初始化计数器
-
递归函数
fun1:- 终止条件:若当前节点为空,直接返回。
- 累加前缀和:将当前节点的值累加到
sum中,得到从根节点到当前节点的完整前缀和。 - 查找合法路径:遍历
arr中存储的历史前缀和,若sum - arr[i] == targetSum,说明存在一条满足条件的子路径,count自增。 - 状态记录:将当前
sum压入arr,供后续的子孙节点匹配使用。 - 向下递归:继续遍历左子树和右子树。
- 回溯(状态还原):在退出当前递归层之前,执行
arr.pop_back(),将当前节点的sum移出数组,保证该分支的数据不会污染其他同级或上级分支。
class Solution {
public:
int count;
int pathSum(TreeNode* root, int targetSum) {
count = 0;
vector<long long> arr;
arr.push_back(0);
fun1(root, arr, 0, targetSum);
return count;
}
void fun1(TreeNode* node, vector<long long>& arr, long long sum, int targetSum) {
if (node == NULL) {
return;
}
sum += node->val;
// 查找是否存在符合条件的历史前缀和
for (int i = 0; i < arr.size(); i++) {
if (sum - arr[i] == targetSum) {
count++;
}
}
// 记录当前前缀和
arr.push_back(sum);
// 递归遍历左右子树
fun1(node->left, arr, sum, targetSum);
fun1(node->right, arr, sum, targetSum);
// 回溯:移除当前节点的前缀和,避免影响其他分支
arr.pop_back();
}
};
3. 复杂度分析与细节说明
- 时间复杂度:O(N⋅H) 其中 N 为二叉树的节点总数,H 为树的高度。遍历每个节点耗时 O(N);在每个节点处,需要遍历长度最大为 H 的前缀和数组。对于平衡二叉树,时间复杂度为 O(NlogN);最坏情况(树退化为链表)下为 O(N2)。
- 空间复杂度:O(H) 由于通过引用传递数组,未开辟额外数组空间。主要空间开销为递归调用栈的深度以及
arr数组的长度,二者均不超过树的高度 H。
