题目
题解
相似题目:
112.路径总和【简单】
113. 路径总和 II【中等】
这两题所求路径都是根到叶子节点的,本题既不要求路径从根节点开始,也不要求在叶子节点结束
DFS
class Solution {
public int pathSum(TreeNode root, int targetSum) {
if(root==null)
return 0;
int res=rootSum(root,targetSum);//从root开始的路径
res+=pathSum(root.left,targetSum);//从root左子开始的路径
res+=pathSum(root.right,targetSum);//从root右子开始的路径
return res;
}
public int rootSum(TreeNode root,long targetSum){
if(root==null)
return 0;
int res=0;
long value=root.val;//要用long型,不然有些用例过不了
if(value==targetSum)
res++;
res+=rootSum(root.left,targetSum-value);
res+=rootSum(root.right,targetSum-value);
return res;
}
}
时间复杂度: O ( n 2 ) O(n^2) O(n2),对于每一个节点,求以该节点为起点的路径数目时,则需要遍历以该节点为根节点的子树的所有节点,因此求该路径所花费的最大时间为 O(n),我们会对每个节点都求一次以该节点为起点的路径数目,因此时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
空间复杂度: O ( n ) O(n) O(n)
前缀和
仔细想想,方法一中其实有很多重复计算,比如计算以root为起点的路径时会计算到以其左子为起点的路径,计算以其左子为起点的路径时同样会计算到该路径。
为了改进方法一,我们定义节点的前缀和为:由根结点到当前结点的路径上所有节点的和。
算法
对二叉树进行先序遍历,
- 预先保存空路径,前缀和为0;
- 假设当前访问节点为node,root到node路径为root->p1->p2->…->pk->node,此时已经保存了p1,p2,…,pk的前缀和,并且计算出node的前缀和;
- 假设node前缀和为curr,此时在已保存的前缀和中查找是否有前缀和==curr-targetSum。如果pi的前缀和符合要求,则p{i+1}到node的路径和一定为targetSum;
- 退出当前节点时,及时更新已经保存的前缀和。
注意
由于我们只能统计往下的路径,但是树的遍历会同时搜索两个方向的子树。因此我们应当在搜索完以某个节点为根的左右子树之后,应当回溯地将路径总和从哈希表中删除,防止统计到跨越两个方向的路径。
class Solution {
Map<Long,Integer>prefix=new HashMap<>();//存储前缀和以及对应数量
public int pathSum(TreeNode root, int targetSum) {
prefix.put(0L,1);//'0L'代表强制把0转换为long型,否则0默认按int型处理
return dfs(root,0,targetSum);
}
public int dfs(TreeNode root,long curr,long targetSum){
if(root==null)
return 0;
int res=0;
curr+=root.val;//更新前缀和
res=prefix.getOrDefault(curr-targetSum,0);//计算curr-targetSum数量
prefix.put(curr,prefix.getOrDefault(curr,0)+1);//保存当前节点前缀和
res+=dfs(root.left,curr,targetSum);
res+=dfs(root.right,curr,targetSum);
prefix.put(curr,prefix.getOrDefault(curr,0)-1);//回溯
return res;
}
}
时间复杂度: O ( n ) O(n) O(n),利用前缀和只需遍历一次二叉树即可
空间复杂度: O ( n ) O(n) O(n)
最后可以看下两种方法的执行用时,差距还是挺大的