前言
本文基于对学习前缀和遇到的问题和理解,总结下来;每个人的理解方式都有差异,希望可以帮到各位;
存在问题
可能会在这些问题上纠结一些时间:
- 前缀和的定义
- 为什么要用前缀和来解决此题
- HashMap是用来干什么的
- 为什么要恢复状态
一、前缀和的定义
一句话概括就是:从该节点到某一节点的路径上,所有结点的val之和;
如下图(原题栗子):
解释:
例如
10 -> 5 -> 3 这条路径各节点val之和就是结点3的前缀和;
10 -> 5 -> 2 -> 1这条路径各结点val之和就是结点1的前缀和;
二、为什么要用前缀和来解决此题
题目要求:
我们在遍历二叉树的时候更容易拿到的是从根节点向下遍历得到的路径,而本题的要求是从某一结点出发,若是暴力解决的话,时间复杂度为O(n^2),无法通过,所以就可以通过前缀和来解决;
具体的,我们可以这样做:
这时,基于前面对前缀和定义的了解,不难理解:
两节点间的路径之和 = 结束结点的前缀和 - 开始结点的前缀和;
也就是说,假设f(x)函数表示前缀和,那么
两节点间的路径之和 = f(end) - f(start);
如下图:
解释:
结点3的前缀和 = 1 + 3 = 4;
结点7的前缀和 = 1 + 3 + 5 + 7 = 16;
f(end) - f(start) = 12;
所以,结点3 到 结点7 有一条路径符合要求(5 - > 7);
这时,问题就转化了!!!,遍历整棵树的每一个结点,记录下每一个结点的前缀和,然后检测该结点的祖先结点中符合条件的个数,最后将满足要求的数量加到最终结果上;
三、HashMap是用来干什么的
满足当前结点到祖先结点中路径之和等于k的路径,有可能不止一条路径(还可能有其他的祖先节点也满足),也就是说祖先节点中可能会有前缀和相同的俩个祖先结点 如下图:
解释:
前缀和为1的结点:结点1, 结点0;
所以路径和为5的结点有两条:
- 0 - > 5;
- 5本身;
HashMap用来记录前缀和相同的路径路径数量,key为前缀和,value是该前缀和的结点数量
四、为什么要恢复状态
因为只能是从祖先结点到子结点,是向下的,所以在更新HashMap时,只对子节点有效;
如下图:
当遍历到最左边的叶子结点4时,对于他来说,前缀和为4的只有结点A,没有B,因为B向下找不到叶子结点4;若不恢复状态,那么在遍历完左树后遍历到右树的叶子结点6时,他就会认为A,B都是满足要求的结点,而导致重复错误,所以,遍历完一个结点的子节点后,要将其从map中删掉;
最终代码
class Solution {
private Map<Long, Integer> map = new HashMap<>();//保存前缀和
private int target;
public int pathSum(TreeNode root, int targetSum) {
this.target = targetSum;
map.put(0L, 1);//必须要有一个前缀和为0的
return dfs(root, 0);
}
private int dfs(TreeNode root, long sum) {
if(root == null) {
return 0;
}
sum += root.val;
//拿到需要的前缀和
int request = map.getOrDefault(sum - target, 0);//需要的前缀和就是当前和减去目标
//保存当前前缀树的值
map.put(sum, map.getOrDefault(sum, 0) + 1);
//递归看左右子树
int left = dfs(root.left, sum);
int right = dfs(root.right, sum);
//恢复状态
map.put(sum, map.get(sum) - 1);
return left + right + request;//返回所有满足的
}
}