给定一个二叉树的根节点 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
提示:
我们首先想到的解法是穷举所有的可能,我们访问每一个结点 node \textit{node} node,检测以 node \textit{node} node 为起始结点且向下延深的路径有多少种。我们递归遍历每一个结点的所有可能的路径,然后将这些路径数目加起来即为返回结果。
我的
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
int psum = 0; // 二叉树的所有路径中值之和为 targetSum 的路径个数
long pval = 0; // 当前路径上的值之和
long target= 0; // targetSum
public:
int pathSum(TreeNode* root, int targetSum){
if(root == nullptr) return 0;
target = targetSum;
valueSum(root); // 以 root 为起点的值和为 targetSum 的路径个数
pathSum(root->left, targetSum);
pathSum(root->right, targetSum);
return psum;
}
void valueSum(TreeNode* root){
if(root == nullptr) return;
pval += root->val;
if(pval == target) psum++;
valueSum(root->left);
valueSum(root->right);
pval -= root->val;
}
};
Leetcode 官方题解
class Solution {
public:
int rootSum(TreeNode* root, int targetSum) {
if (!root) return 0;
int ret = 0;
if (root->val == targetSum) ret++;
ret += rootSum(root->left, targetSum - root->val);
ret += rootSum(root->right, targetSum - root->val);
return ret;
}
int pathSum(TreeNode* root, int targetSum) {
if (!root) return 0;
int ret = rootSum(root, targetSum);
ret += pathSum(root->left, targetSum);
ret += pathSum(root->right, targetSum);
return ret;
}
};
时间复杂度: O ( N 2 ) O(N^2) O(N2),其中 N N N 为该二叉树结点的个数。对于每一个结点,求以该结点为起点的路径数目时,则需要遍历以该结点为根结点的子树的所有结点,因此求该路径所花费的最大时间为 O ( N ) O(N) O(N),我们会对每个结点都求一次以该结点为起点的路径数目,因此时间复杂度为 O ( N 2 ) O(N^{2}) O(N2)。
空间复杂度: O ( N ) O(N) O(N),考虑到递归需要在栈上开辟空间。
我们仔细思考一下,解法一(DFS)中应该存在许多重复计算——重复计算相同路径。此时我们就想起之前的一道题,即数组的前缀和。
定义结点的前缀和为:由根结点到当前结点的路径上所有结点的和。
所以可以用先序遍历二叉树,记录下根结点 root \textit{root} root 到当前结点 p p p 的路径上除当前结点以外所有结点的前缀和,在已保存的路径前缀和中查找是否存在前缀和刚好等于当前结点到根结点的前缀和 c u r r curr curr 减去 targetSum \textit{targetSum} targetSum。
我写的
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
int psum = 0;
long lastval = 0;
unordered_map<long, int> pathval; // <前缀和, 个数>
public:
int pathSum(TreeNode* root, int targetSum){
if(root == nullptr) return psum;
long nowval = 0;
if(pathval.size() == 0){
nowval = root->val;
lastval = nowval;
pathval.emplace(nowval, 1);
}else{
nowval = root->val + lastval;
lastval = nowval;
if(pathval.count(nowval) != 0) pathval[nowval]++;
else pathval.emplace(nowval, 1);
}
pathSum(root->left, targetSum);
lastval = nowval;
pathSum(root->right, targetSum);
if(pathval.size() != 1 || pathval.begin()->second != 1){ // 是不是根结点
if(nowval == targetSum) psum++;
if(pathval.count(nowval - targetSum) != 0){
if(targetSum == 0)// 为了防止 targetSum 为0
psum += pathval[nowval] - 1;
else psum += pathval[nowval - targetSum];
}
if(pathval[nowval] != 1) --pathval[nowval];
else pathval.erase(nowval);
}else if(pathval.count(targetSum) != 0) psum++;
return psum;
}
};
Leetcode 官方题解
class Solution {
public:
unordered_map<long long, int> prefix;
int dfs(TreeNode *root, long long curr, int targetSum) {
if (!root) {
return 0;
}
int ret = 0;
curr += root->val;
if (prefix.count(curr - targetSum)) {
ret = prefix[curr - targetSum];
}
prefix[curr]++;
ret += dfs(root->left, curr, targetSum);
ret += dfs(root->right, curr, targetSum);
prefix[curr]--;
return ret;
}
int pathSum(TreeNode* root, int targetSum) {
prefix[0] = 1;
return dfs(root, 0, targetSum);
}
};
网友简洁写法:
每个结点的过程是 : 求当前结点结束的路径数量,将以当前结点为结尾的前缀和入表,递归当前结点的左右子树,回溯(本结点前缀和出表)
class Solution {
public:
unordered_map<int64_t, int> m{{0,1}};
int pathSum(TreeNode* root, int targetSum, int64_t sum = 0) {
if(!root) return 0;
int v = (m.count(sum + root->val - targetSum) ? m[sum + root->val - targetSum] : 0);
++m[sum + root->val];
int ret = pathSum(root->left, targetSum, sum + root->val) + pathSum(root->right, targetSum, sum + root->val);
if(--m[sum + root->val] == 0) m.erase(sum + root->val);
return v + ret;
}
};
时间复杂度: O ( N ) O(N) O(N),其中 N N N 为二叉树中节点的个数。利用前缀和只需遍历一次二叉树即可。
空间复杂度: O ( N ) O(N) O(N),主要是递归所使用的空间和哈希表。