title: 437 路径总和
tags:
notebook: leetcode
####题目介绍
给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。
路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/path-sum-iii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
####题目样例
示例 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
通过次数119,910提交次数209,171
本题数据在 1 e 3 1e3 1e3,所以显然本题是可以接受 O ( n 2 ) O(n^{2}) O(n2)的时间复杂度的。
这道题目比较有意思的地方在于树的路径
这里路径的要求是只能向下,
所以原本的树形结构我们可以看做所有节点节点至多有两个出边和一个入边的点这样的图。
由于一个节点入度至多唯一,那么我们就可以发现对于节点i,仅与父节点有一条路径(对于相邻两层来说), 那么扩展到n层我们发现结论每个结点都有一条唯一的路径
而由于树形结构的单链表特性,我们发现:
对
于
路
径
R
o
u
t
e
(
r
o
o
t
,
n
o
d
e
)
,
对于路径Route(root,node),
对于路径Route(root,node),
∀
i
∈
(
r
o
o
t
,
n
o
d
e
)
,
都
满
足
R
o
u
t
e
(
i
,
n
o
d
e
)
⊆
R
o
u
t
e
(
r
o
o
t
,
n
o
d
e
)
\forall i\in (root,node) , 都满足Route(i,node)\subseteq Route(root,node)
∀i∈(root,node),都满足Route(i,node)⊆Route(root,node)
因此我们发现根节点到某个节点的路径是一个线性表且是前缀和的线性表,从而问题转换成了前缀树 的求解问题
因为dfs的复杂度是基于深度的,(实际我们就是在遍历从根节点到叶子节点的路径,长度就是 l o g n logn logn的因此空间复杂度仅为( O ( l o g n ) O(logn) O(logn)))
因此整体的算法思想为
1.查询前缀和,计算以当前节点作为终点,满足路径和的节点个数
2.计算左右子树满足路径和的路径个数
左右子树是简单的递归这里不再讲解了。
这里的关键在于前缀和的计算。
因为这里采用的是dfs而不是回溯,所以我们计算的是从根节点开始的前缀和。
而我们想要是从当前结点向上的前缀和,(与前缀和的顺序完全相反)
因此如果想要正序的话还需要
O
(
n
)
O(n)
O(n)的复杂度,这样复杂度就到
O
(
n
2
)
O(n^2)
O(n2)了。
因此我们需要找到方法维持前缀和
O
(
1
)
O(1)
O(1)的优点
我们发现根据前缀和定义
S
i
,
j
=
s
u
m
i
−
s
u
m
j
−
1
S_{i,j} = sum_i - sum_{j-1}
Si,j=sumi−sumj−1
(由于j-1,需要设置sum0 = 0)
通过这个关系式,前缀和的查询又恢复到了O(1)了。
因此我们就可以计算以当前结点作为终点路径和等于目标值的路径个数了
s
u
m
i
,
t
a
r
g
e
t
n
u
m
sum_i,targetnum
sumi,targetnum已知,
t
a
r
g
e
t
n
u
m
=
s
u
m
i
−
s
u
m
j
−
1
targetnum =sum_i - sum_{j-1}
targetnum=sumi−sumj−1
s
u
m
j
−
1
=
s
u
m
i
−
t
a
r
g
e
t
n
u
m
sum_{j-1} = sum_i - targetnum
sumj−1=sumi−targetnum
因此如上式,
当
s
u
m
j
−
1
=
=
s
u
m
i
−
t
a
r
g
e
t
n
u
m
sum_{j-1} == sum_i - targetnum
sumj−1==sumi−targetnum,则
j
−
1
j-1
j−1为以i为终点的一个值为
t
a
r
g
e
t
n
u
m
targetnum
targetnum的路径
因此在算法的第一步需要通过logn的复杂度找出前缀和中值等于 上式的值。
整体复杂度为O(nlogn)
算法实现
/**
* 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 {
private:
int sum;
unordered_map<int,int> m;
//之前一直忘了map的查询效率是O(logn)的所以在进行logn的查询以及前缀和操作是非常合适的并且还能进行计数避免多个重复值
public:
int pathSum(TreeNode* root, int targetSum) {
//m.clear();
sum = 0;
//记录当前路径和
m[0] = 1;
//因为前缀和定义需要讲sum0 = 0(记录(0,i)的路径和)
return dfs(root,targetSum);
}
int dfs(TreeNode* root, const int& targetSum){
//根据前缀和的定义 targetsum = num[k]-num[i-1]
//而目前我们需要查找的就是所有满足上式的num[i-1]的个数
if(!root){
return 0;
}
sum +=root->val;
//当前层路径和
int &&diff = sum-targetSum;
const int& num = m[diff];
//满足的个数为前缀和为diff的个数
m[sum]++;
//查询左右子树,将当前路径和加入集合
//对于第k层来说存在(i,k)路径使得满足路径和
int &&left = dfs(root->left,targetSum);
int &&right = dfs(root->right,targetSum);
m[sum]--;
sum-=root->val;
//计算结束回溯删除当前节点
return left+right+num;
}
Solution(){
}
};