力扣解题思路:路径总和 纠错记录

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);!!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值