lc_rt_437_pathSum


题目:路径总和 III simple

给定一个二叉树,它的每个结点都存放着一个整数值。
找出路径和等于给定数值的路径总数。
路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。

示例:
root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8

      10
     /  \
    5   -3
   / \    \
  3   2   11
 / \   \
3  -2   1

返回 3。和等于 8 的路径有:
1.  5 -> 3
2.  5 -> 2 -> 1
3.  -3 -> 11

 

 

 

package leetCode.Recursion_Tacktrace;


import java.util.HashMap;
import java.util.Map;

public class lc_rt_437_pathSum {

/*
    思路:

    1)双重递归:以每一个结点为起点,考虑所有符合要求的子序列,时间复杂度高
    2)前缀和:以每一个结点为终点,考虑所有符合要求的子序列(更好),时间复杂度低

 */

    /*
    原文:https://leetcode-cn.com/problems/path-sum-iii/solution/437lu-jing-zong-he-iii-di-gui-fang-shi-by-ming-zhi/

        双重递归:时间复杂度大,但好理解
        本题需要去计算路径和等于给定数值的路径总数,我们依旧遵循树模型的解题思路,
     按照递归的方式去求解(递归的一个重要思想就是两部分:
        1.找到最简单的子问题求解,
        2.其他问题不考虑内在细节,只考虑整体逻辑),
        那我们现在来设计递归关系:
        首先,最简单的子问题是什么呢?由于这道题是在树的框架下,我们最容易想到的就是遍历的终止条件:
        if(root == null){
            return 0;
        }

        接下来,我们来考虑再上升的一个层次,题目要求 路径不需要从根节点开始,
     也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点) 。
     这就要求我们只需要去求三部分即可:
        1)以当前节点作为头结点的路径数量
        2)以当前节点的左孩子作为头结点的路径数量
        3)以当前节点的右孩子作为头结点啊路径数量
        将这三部分之和作为最后结果即可。

        最后的问题是:我们应该如何去求以当前节点作为头结点的路径的数量?
        这里依旧是按照树的遍历方式模板,每到一个节点让sum-root.val,并判断sum是否为0,
     如果为零的话,则找到满足条件的一条路径。


    */

    //  本方法递归是为了使每个结点都成为子路径的头结点,这样每条用countThisNode计算的
    //  路径的头结点都不同,就不会重复
    public int pathSum2(TreeNode root, int sum) {
        if (root == null)
            return 0;
        int res = countThisNode(root, sum);
        int lres = pathSum2(root.left, sum);//每条路径都以sum为目标
        int rres = pathSum2(root.right, sum);
        return res + lres + rres;
    }

    //    以当前节点node作为头结点其子路径和=sum的路径的数量,
    // 本方法递归是在1条路径上不断求sum,找到所有满足条件的sum
    public int countThisNode(TreeNode node, int sum) {
        if (node == null) {
            return 0;
        }
        sum -= node.val;
        int res = (sum == 0 ? 1 : 0);
//      如果sum=0则到当前结点止的子路径和为sum,条数+1,
//      继续至左右子节点寻找,直至叶子结点
        return res
                + countThisNode(node.left, sum)
                + countThisNode(node.right, sum);
//      这里sum是基本变量,不会改变值,所以不需要恢复,pathSum2中,map是对象
//      会改变其他层的值,要恢复
    }


    /*
原文:https://leetcode-cn.com/problems/path-sum-iii/solution/qian-zhui-he-di-gui-hui-su-by-shi-huo-de-xia-tian/
    前缀和:O(n)
    这道题用到了一个概念,叫前缀和。就是到达当前元素的路径上,之前所有元素的和。
    如果两个数的前缀总和是相同的,那么两节点之间的元素总和为零。
    进一步扩展相同的想法,如果前缀总和currSum,在节点A和节点B处相差target,
则位于节点A和节点B之间的元素之和是target。
    因为本题中的路径是一棵树,从根往任一节点的路径上(不走回头路),有且仅有一条路径,
因为不存在环。(如果存在环,前缀和就不能用了,需要改造算法)
    抵达当前节点(即B节点)后,将前缀和累加,然后查找在前缀和上,
有没有前缀和currSum-target的节点(即A节点),存在即表示从A到B有x条路径之和满足条件的情况。
结果res加上满足前缀和currSum-target的节点的数量。然后递归进入左右子树。
    左右子树遍历完成之后,回到当前层,需要把当前节点添加的前缀和去除。
避免回溯之后影响上一层。因为思想是前缀和,回溯后之前的curSum就不再属于前缀了,我们就要去掉它



     */
    public int pathSum(TreeNode root, int sum) {
        if (root == null)
            return 0;
        // key是前缀和, value是大小为key的前缀和出现的次数
        HashMap<Integer, Integer> map = new HashMap<>();
        //!!!重要,前缀和为0的一条路径,根节点的前缀和为0(无数据),且有1条,如果不写的话出错
        map.put(0, 1);
        // 前缀和的递归回溯思路
        return pathSumCore(root, map, sum, 0);

    }

    /**
     * 前缀和的递归回溯思路
     * 从当前节点反推到根节点(反推比较好理解,正向其实也只有一条),有且仅有一条路径,因为这是一棵树
     * 如果此前有和为currSum-target,而当前的和又为currSum,两者的差就肯定为target了
     * 所以前缀和对于当前路径来说是唯一的,当前记录的前缀和,在回溯结束,回到本层时去除,保证其不影响其他分支的结果
     *
     * @param node   树节点
     * @param map    前缀和Map
     * @param target 目标值
     * @param curSum 当前路径和
     * @return 满足题意的解
     */


    public int pathSumCore(TreeNode node, Map<Integer, Integer> map, int target, int curSum) {
        // 1.递归终止条件
        if (node == null)
            return 0;
        // 2.本层要做的事情
        curSum += node.val;
        int res = 0;//保存以当前结点为终点,符合要求的前缀和的数量

        //---核心代码
        // 看看root到当前节点这条路上是否存在节点前缀和加target为currSum的路径
        // 当前节点->root节点反推,有且仅有一条路径,如果此前有和为currSum-target,
        // 而当前的和又为currSum,两者的差就肯定为target了
        // currSum-target相当于找路径的起点,起点的sum+target=currSum,
        // 当前点到起点的距离就是target
        res = map.getOrDefault(curSum - target, 0);
        // 更新路径上当前节点前缀和的个数
        map.put(curSum, map.getOrDefault(curSum, 0) + 1);
        //---核心代码

        // 3.进入下一层
        int total = res
                + pathSumCore(node.left, map, target, curSum)
                + pathSumCore(node.right, map, target, curSum);
        // 4.!!!重要,回到本层,恢复状态,去除当前节点的前缀和数量,否则会影响到接下来的结点
//        这里map是对象,会改变值,所以要恢复,pathSum2中sum是基本变量,
//      不会改变其他层的值,不用恢复
        map.put(curSum, map.get(curSum) - 1);
        return total;
    }

}

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int x) {
        val = x;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值