LeetCode Top 100 Liked Questions 437. Path Sum III (Java版; Easy)

welcome to my blog

LeetCode Top 100 Liked Questions 437. Path Sum III (Java版; Easy)

题目描述
You are given a binary tree in which each node contains an integer value.

Find the number of paths that sum to a given value.

The path does not need to start or end at the root or a leaf, but it must go downwards (traveling only from parent nodes to child nodes).

The tree has no more than 1,000 nodes and the values are in the range -1,000,000 to 1,000,000.

Example:

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

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

Return 3. The paths that sum to 8 are:

1.  5 -> 3
2.  5 -> 2 -> 1
3. -3 -> 11
class Solution {
    public int pathSum(TreeNode root, int sum) {
        if(root==null){
            return 0;
        }
        //前缀和
        HashMap<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);
        return backtrace(map, root, 0, sum);
        
    }

    private int backtrace(HashMap<Integer, Integer> map, TreeNode root, int cur, int sum){
        //
        if(root==null){
            return 0;
        }
        int res = 0;
        cur = cur + root.val;
        if(map.containsKey(cur - sum)){
            res = map.get(cur - sum);
        }
        map.put(cur, map.getOrDefault(cur, 0)+1);
        res = res + backtrace(map, root.left, cur, sum);
        res = res + backtrace(map, root.right, cur, sum);
        map.put(cur, map.get(cur)-1);
        return res;
    }
}
class Solution {
    public int pathSum(TreeNode root, int sum) {
        HashMap<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);
        return core(map, root, 0, sum);
    }

    private int core(HashMap<Integer, Integer> map, TreeNode root, int curSum, int sum){
        if(root==null){
            return 0;
        }
        curSum = curSum + root.val;
        int res = map.getOrDefault(curSum - sum, 0);
        map.put(curSum, map.getOrDefault(curSum, 0)+1);
        res += core(map, root.left, curSum, sum);
        res += core(map, root.right, curSum, sum);
        map.put(curSum, map.get(curSum)-1);
        return res;
    }
}
第一次做; 最优解; 使用哈希表记录前缀和; 突然想起一句话:已背; 看注释

-时间复杂度O(N)

int res = preSum.getOrDefault(currSum - target, 0)这句太秀了! 极致地利用了前缀和
比如1→2,target=3; 当前在2处,currSum=3,preSum包含3-3=0这个key, 说明从root到当前节点这条路上存在满足条件的路径

比如1→2,target=2; 当前在2处,currSum=3,preSum包含3-2=1这个key, 说明从root到当前节点这条路上存在满足条件的路径
比如1→1→2,target=2; 当前在2处,currSum=4,preSum包含4-2=2这个key, 说明从root到当前节点这条路上存在满足条件的路径
import java.util.HashMap;


class Solution {
    public int pathSum(TreeNode root, int sum) {
        if(root==null)
            return 0;
        //key是前缀和, value是大小为key的前缀和出现的次数
        HashMap<Integer,Integer> prefixSum = new HashMap<>();
        //非常细节的初始化, 得结合案例体会,比如案例1→1,target=2
        prefixSum.put(0,1);
        return core(prefixSum, sum, root, 0);
    }
    public int core(HashMap<Integer,Integer> prefixSum, int target, TreeNode root, int currSum){
        //base case
        if(root==null)
            return 0;
        currSum = currSum + root.val;
        //先看看root到当前节点这条路上是否存在节点value加和为target的路径
        int res = prefixSum.getOrDefault(currSum-target, 0);
        //更新前缀和的哈希表
        prefixSum.put(currSum, prefixSum.getOrDefault(currSum,0) + 1);
        //新条件新递归; 按照该方式处理左子树和右子树
        int leftRes = core(prefixSum, target, root.left, currSum);
        int rightRes = core(prefixSum, target, root.right, currSum);
        //恢复现场, 从而保证prefixSum中的记录都是投一个branch上的
        prefixSum.put(currSum, prefixSum.get(currSum)-1);
        return res + leftRes + rightRes;
    }
}
第二次做; 在二叉树中使用哈希表前缀和方法一定要记得恢复现场; 哈希表前缀和方法中一定需要的条件:哈希表, preSum, 如果是涉及路经的长度,则还需要元素的位置信息; 本题中的三个核心:1) 初始化sumMap, sumMap.put(0,1), 这是为了找出以根节点开始的情况 2)先判断sumMap中是否有curSum-k, 再把curSum加入到sumMap中 3)恢复现场, 考虑完当前节点的情况后, 要把当前节点对哈希表的改动撤回
//二叉树中使用哈希表前缀和方法一定要记得恢复现场
class Solution {
    public int pathSum(TreeNode root, int sum) {
        if(root==null)
            return 0;
        //key是前缀和, value是前缀和出现的次数
        HashMap<Integer, Integer> sumMap = new HashMap<>();
        //核心初始化:为了保证以root开头的路径能被考虑到
        sumMap.put(0, 1);
        return core(sumMap, root, 0, sum);
    }
    //递归函数逻辑:返回以当前节点为结尾的所有路径中, 前缀和等于k的路径个数
    public int core(HashMap<Integer, Integer> sumMap, TreeNode root, int pre, int k){
        //base case
        if(root==null)
            return 0;
        //
        
        int curSum = pre + root.val;
        //核心: 先判断有没有合适的前缀和, 再把curSum更新到哈希表中, 这两个操作的顺序不能乱, 否则会出错, 如案例[1], k=0, 这个案例的输出应该是0, 但是先把curSum=1加入哈希表后, 哈希表中就有curSum-0的记录了
        //判断有没有合适的前缀和
        int res = sumMap.getOrDefault(curSum-k, 0);
        //核心: 先判断有没有合适的前缀和, 再把curSum更新到哈希表中, 这两个操作的顺序不能乱, 否则会出错, 如案例[1], k=0, 这个案例的输出应该是0, 但是先把curSum=1加入哈希表后, 哈希表中就有curSum-0的记录了
        //把curSum更新到哈希表中
        sumMap.put(curSum, sumMap.getOrDefault(curSum, 0)+1);
        
        //新条件新递归
        //左子树中, 以所有节点为结尾的路径中, 和为k的路径个数
        int leftRes = core(sumMap, root.left, curSum, k);
        //右子树中, 以所有节点为结尾的路径中, 和为k的路径个数
        int rightRes = core(sumMap, root.right, curSum, k);
        //超级核心: 恢复现场
        sumMap.put(curSum, sumMap.get(curSum)-1);
        return res + leftRes + rightRes;
    }
}
第一次做; 双重递归, 要理解每个递归的作用, 第一个递归:以不同的节点作为路径的开始; 第二个递归:从给定的开始节点向下探索是否存在符合条件的路径; 本题还有个大陷阱:满足条件时不能直接返回,而是要继续向下探索,见注释
/**
[1,-2,-3,1,3,-2,null,-1]
-1
答案:4
 */
class Solution {
    public int pathSum(TreeNode root, int sum) {
        if(root==null)
            return 0;
        //对当前条件的处理
        int currRes = core(root, sum);
        //新条件新递归; 注意:不改变sum!!
        int leftRes = pathSum(root.left, sum);
        int rightRes = pathSum(root.right, sum);
        return currRes + leftRes + rightRes;
    }
    public int core(TreeNode root, int sum){
        if(root==null) 
            return 0;
        /*
        双重递归是这道题的特色, 不过这道题有个超级大陷阱, 就是当root.val==sum时,不能直接返回1, 而是要继续向下探索, 为什么? 
        因为如果root的左孩子是a,root的左孩子的左孩子是-a, 那么→root→root.left→root.left.left也是一条符合条件的路径!
        */
        int curr = 0;
        if(root.val == sum)
            curr = 1;
        //
        int leftRes = core(root.left, sum-root.val);
        int rightRes = core(root.right, sum-root.val);
        return curr + leftRes + rightRes;
    }
}
LeetCode最优解; 使用哈希表, key是前缀和, value是某个和出现的次数, 核心:从root开始记录, 到当前节点为止
结合这个例子理解:
For instance : in one path we have 1,2,-1,-1,2, then the prefix sum will be: 1, 3, 2, 1, 3, let's say we want to find target sum is 2, 
then we will have{2}, {1,2,-1}, {2,-1,-1,2} and {2}ways.

思路分析:
This is an excellent idea and took me some time to figure out the logic behind.
Hope my comment here could help understanding this solution.

The prefix stores the sum from the root to the current node in the recursion
The map stores <prefix sum, frequency> pairs before getting to the current node. We can imagine a path from the root to the current node. 
The sum from any node in the middle of the path to the current node = the difference between the sum from the root to the current node and 
the prefix sum of the node in the middle.
We are looking for some consecutive nodes that sum up to the given target value, which means the difference discussed in 2. should equal 
to the target value. In addition, we need to know how many differences are equal to the target value.
Here comes the map. The map stores the frequency of all possible sum in the path to the current node. If the difference between the current 
sum and the target value exists in the map, there must exist a node in the middle of the path, such that from this node to the current node, 
the sum is equal to the target value.
Note that there might be multiple nodes in the middle that satisfy what is discussed in 4. The frequency in the map is used to help with this.
Therefore, in each recursion, the map stores all information we need to calculate the number of ranges that sum up to target. Note that each 
range starts from a middle node, ended by the current node.
To get the total number of path count, we add up the number of valid paths ended by EACH node in the tree.
Each recursion returns the total count of valid paths in the subtree rooted at the current node. And this sum can be divided into three parts:
- the total number of valid paths in the subtree rooted at the current node's left child
- the total number of valid paths in the subtree rooted at the current node's right child
- the number of valid paths ended by the current node
The interesting part of this solution is that the prefix is counted from the top(root) to the bottom(leaves), and the result of total count 
is calculated from the bottom to the top :D

The code below takes 16 ms which is super fast.

补充说明:
I just wanted to add one additional point, which took me some time to think through. I was wondering how you can keep track of a sequence of 
sums in a 1D hash table, when a tree can have multiple branches, how do you keep track of duplicate sums on different branches of the tree? 
The answer is that this method only keeps track of 1 branch at a time. Because we're doing a depth first search, we will iterate all the way 
to the end of a single branch before coming back up. However, as we're coming back up, we're removing the nodes at the bottom of the branch 
from our hash table, using line map.put(sum, map.get(sum) - 1);, before ending the function.

To summarize, the hash table is only keeping track of a portion of a single branch at any given time, from the root node to the current node only.
public int pathSum(TreeNode root, int sum) {
        if (root == null) {
            return 0;
        }
        Map<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);
        return findPathSum(root, 0, sum, map);  
    }
    private int findPathSum(TreeNode curr, int sum, int target, Map<Integer, Integer> map) {
        if (curr == null) {
            return 0;
        }
        // update the prefix sum by adding the current val
        sum += curr.val;
        // get the number of valid path, ended by the current node
        int numPathToCurr = map.getOrDefault(sum-target, 0); 
        // update the map with the current sum, so the map is good to be passed to the next recursion
        map.put(sum, map.getOrDefault(sum, 0) + 1);
        // add the 3 parts discussed in 8. together
        int res = numPathToCurr + findPathSum(curr.left, sum, target, map)
                                               + findPathSum(curr.right, sum, target, map);
       // restore the map, as the recursion goes from the bottom to the top
        map.put(sum, map.get(sum) - 1);
        return res;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值