113. 路径总和 II
思路:给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。最简单的方式就是DFS,代码比较简单:
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int sum) {
if(root == null) return res;
dfs(root,sum,new ArrayList<>());
return res;
}
public void dfs(TreeNode root,int sum,List<Integer> list){
if(root == null){
return;
}
list.add(root.val);
if(sum == root.val && root.left == null && root.right == null){
res.add(new ArrayList<>(list));
return;
}
dfs(root.left,sum-root.val,list);
dfs(root.right,sum-root.val,list);
list.remove(list.size()-1);
}
代码没有语法错误,但是运行结果有问题,回忆一下,我的递归函数的出口当root==null就退出 或者res.add了之后也不需要要继续执行了(因为都到叶子节点了)看似都没有问题啊,为什么错了呢?
首先,对于第一种情况root为空为出口肯定没有错,但是我们有必要将res.add之后作为出口吗,我觉得没必要,因为即使res.add后不执行return,然后程序继续执行完之后两个dfs和一个list.remove后也会自动结束:
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int sum) {
if(root == null) return res;
dfs(root,sum,new ArrayList<>());
return res;
}
public void dfs(TreeNode root,int sum,List<Integer> list){
if(root == null){
return;
}
list.add(root.val);
if(sum == root.val && root.left == null && root.right == null){
res.add(new ArrayList<>(list));
//return;
}
dfs(root.left,sum-root.val,list);
dfs(root.right,sum-root.val,list);
list.remove(list.size()-1);
}
现在就运行正确了,原因就很明显了,因为直接返回的话,最后一个元素没有移除,会导致回溯出错。我是个比较执着的人,那如果我一定要用return呢!( ఠൠఠ )ノ
同样的,不需要判断if(sum<0) return;的情况,因为我们使用的回溯法,所以需要回溯,即使“此路不通”也是需要回溯的不能直接return;。
那就在返回之前移除最后一个元素就好了呀(●ˇ∀ˇ●)
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int sum) {
if(root == null) return res;
dfs(root,sum,new ArrayList<>());
return res;
}
public void dfs(TreeNode root,int sum,List<Integer> list){
if(root == null){
return;
}
if(sum == root.val && root.left == null && root.right == null){
list.add(root.val);
res.add(new ArrayList<>(list));
list.remove(list.size()-1);
return;
}
list.add(root.val);
dfs(root.left,sum-root.val,list);
dfs(root.right,sum-root.val,list);
list.remove(list.size()-1);
}
437. 路径总和 III
思路:
先上我的错误答案:
public int res;
public int pathSum(TreeNode root, int sum) {
dfs(root,sum);
return res;
}
public void dfs(TreeNode root,int sum){
if(root == null || sum<0) return;
//int sum = dfs(root.left) + dfs(root.right);
if(sum == root.val){
res++;
//return;
}
dfs(root.left,sum-root.val);
dfs(root.right,sum-root.val);
}
题目要求路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点),而我这里却是从根节点开始的。。。
那好说,我用双重递归不就行了吗:
int pathnumber;
public int pathSum(TreeNode root, int sum) {
if(root == null) return 0;
Sum(root,sum);
pathSum(root.left,sum);
pathSum(root.right,sum);
return pathnumber;
}
public void Sum(TreeNode root, int sum){
if(root == null) return;
sum-=root.val;
if(sum == 0){
patjhnumber++;
return;
}
Sum(root.left,sum);
Sum(root.right,sum);
}
然后我又错了(っ °Д °;)っ我发现我对这个return真的有执念,这里if(sum == 0)时不能return,因为还要继续往后遍历,直到root为空才能停止这条路径的搜索!!!所以去点return:
int pathnumber;
public int pathSum(TreeNode root, int sum) {
if(root == null) return 0;
Sum(root,sum);
pathSum(root.left,sum);
pathSum(root.right,sum);
return pathnumber;
}
public void Sum(TreeNode root, int sum){
if(root == null) return;
sum-=root.val;
if(sum == 0){
pathnumber++;
//return;不能返回,要继续往下找
}
Sum(root.left,sum);
Sum(root.right,sum);
}
这个递归的时间复杂度还是挺高的,所以得想方法优化他。我们知道在求数组和的时候我们用到了一种叫前缀和的方法,如果在这一题中,我们把每条路径都作为一个数组,记录其前缀和,那么在求路径上任意两点的路径和不就更容易了吗?
那我们就从根节点开始,创建一个数组来保存从根节点到当前节点路径的所有节点值,需要计算路径和的时候,就顺着该数组的当前位置倒着向前遍历直到到达根节点,只要满足条件的,都可以将路径个数加一:
//遍历每个节点。 关键点:递归
//计算以当前节点为路径终点的所有路径和。 关键点:用一个数组保存从根节点到当前节点路径
//代码
public int pathSum(TreeNode root, int sum) {
return pathSum(root, sum, new int[1000], 0);
}
public int pathSum(TreeNode root, int sum, int[] array/*保存路径*/, int p/*指向路径终点*/) {
if (root == null) {
return 0;
}
int tmp = root.val;
int n = root.val == sum ? 1 : 0;
for (int i = p - 1; i >= 0; i--) {
tmp += array[i];
if (tmp == sum) {
n++;
}
}
array[p] = root.val;
int n1 = pathSum(root.left, sum, array, p + 1);
int n2 = pathSum(root.right, sum, array, p + 1);
array[p] = 0;
return n + n1 + n2;
}
注意一下,这里的array[p] = 0;其实可以不要,这个是递归过程中的回溯操作,但是因为我们遍历这个数组时都是从i = p - 1开始的,所以其实没必要设置array[p] = 0;因为递归完左子树后,右子树再递归就会覆盖掉左子树修改的数组值,因此是不会影响的!!!
既然可以用数组,当然也可以用map保存前缀和啊,这不是常规操作嘛:
public int pathSum(TreeNode root, int sum) {
// key是前缀和, value是大小为key的前缀和出现的次数
Map<Integer, Integer> prefixSumCount = new HashMap<>();
// 前缀和为0的一条路径
prefixSumCount.put(0, 1);
// 前缀和的递归回溯思路
return recursionPathSum(root, prefixSumCount, sum, 0);
}
/**
* 前缀和的递归回溯思路
* 从当前节点反推到根节点(反推比较好理解,正向其实也只有一条),有且仅有一条路径,因为这是一棵树
* 如果此前有和为currSum-target,而当前的和又为currSum,两者的差就肯定为target了
* 所以前缀和对于当前路径来说是唯一的,当前记录的前缀和,在回溯结束,回到本层时去除,保证其不影响其他分支的结果
* @param node 树节点
* @param prefixSumCount 前缀和Map
* @param target 目标值
* @param currSum 当前路径和
* @return 满足题意的解
*/
private int recursionPathSum(TreeNode node, Map<Integer, Integer> prefixSumCount, int target, int currSum) {
// 1.递归终止条件
if (node == null) {
return 0;
}
// 2.本层要做的事情
int res = 0;
// 当前路径上的和
currSum += node.val;
res += prefixSumCount.getOrDefault(currSum - target, 0);
// 更新路径上当前节点前缀和的个数
prefixSumCount.put(currSum, prefixSumCount.getOrDefault(currSum, 0) + 1);
// 3.进入下一层
res += recursionPathSum(node.left, prefixSumCount, target, currSum);
res += recursionPathSum(node.right, prefixSumCount, target, currSum);
// 4.回到本层,恢复状态,去除当前节点的前缀和数量
prefixSumCount.put(currSum, prefixSumCount.get(currSum) - 1);
return res;
}
注意map需要初始化prefixSumCount.put(0, 1);,因为可能当前节点到根节点的路径和就等于target!!并且还需要注意的是,用map一定要有回溯的过程,因为map的值不会像上一种解法一样被下一轮递归覆盖(没有记录层数p),所以遍历完一层就要删除该路径和prefixSumCount.put(currSum, prefixSumCount.get(currSum) - 1);!!