【Leetcode】437. Path Sum III

题目地址:

https://leetcode.com/problems/path-sum-iii/

给定一个数sum,求二叉树中从某个节点向下走的和为给定数的路径个数。

法1:分治法。所有的路径可以被分为两部分,一部分是包含根节点的,另一部分是不包含根节点的。我们需要单独设一个函数,用来计算从某个根节点出发,和为sum的路径个数。代码如下:

public class Solution {
    public int pathSum(TreeNode root, int sum) {
        if (root == null) {
            return 0;
        }
        
        // 路径可分为两种类型,其一是包含根节点的,其二是不包含根节点的
        int res = findPath(root, sum);
        res += pathSum(root.left, sum);
        res += pathSum(root.right, sum);
        
        return res;
    }
    
    // 返回从root出发,和为num的路径个数
    private int findPath(TreeNode root, int num) {
        if (root == null) {
            return 0;
        }
        
        // 这样的路径有两类:
        // 第一类是只包含根节点;
        // 第二类是根节点作为和的一部分,子树作为和的另一部分的路径;
        int res = 0;
        if (root.val == num) {
            res += 1;
        }
        
        res += findPath(root.left, num - root.val);
        res += findPath(root.right, num - root.val);
        
        return res;
    }
}

空间复杂度 O ( h ) O(h) O(h),时间复杂度可以这么递推: T ( n ) = n + T ( l ) + T ( r ) = n + n l + n r + T ( l l ) + T ( l r ) + T ( r l ) + T ( r r ) = . . . = n + ( n − 1 ) + ( n − 2 ) . . . + 1 + n = O ( n 2 ) T(n)=n+T(l)+T(r)\\=n+n_l+n_r+T(ll)+T(lr)+T(rl)+T(rr)\\=...=n +(n-1)+ (n-2) ... +1 +n=O(n^2) T(n)=n+T(l)+T(r)=n+nl+nr+T(ll)+T(lr)+T(rl)+T(rr)=...=n+(n1)+(n2)...+1+n=O(n2)所以时间复杂度是 O ( n 2 ) O(n^2) O(n2)

法2:DFS回溯 前缀和。存在符合条件的path当且仅当存在树上的两个前缀和之差等于sum。
可以先用一个map来记录根节点到当前节点为止每个出现过的前缀和的次数(意思是,每个前缀和 p p p在从根到当前节点这个“数组”中出现了多少次,每出现一次,就说明有一条从根到当前节点的路径和为 p p p),所以每次走到一个节点的时候,就查一下当前的前缀和减去sum是否等于map中存储的某个前缀和,如果等于,就调取这个前缀和出现的次数,这样就统计出了从根到当前节点上有多少个片段和为sum了。但需要注意的是,回溯之前要把最新的前缀和减掉 1 1 1,因为已经从当前节点回溯回去了,当前节点这个“选择”已经做完了,需要做下一个选择了,所以要把当前这个前缀和在记忆中从次数减掉 1 1 1

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

public class Solution {
    public int pathSum(TreeNode root, int sum) {
        HashMap<Integer, Integer> prefixSum = new HashMap<>();
        // 前缀和是0表示空路径,也需要加进prefixSum里去
        prefixSum.put(0, 1);
        return dfs(root, 0, sum, prefixSum);
    }
    
    // curSum是从根到root的父亲节点为止的前缀和。
    // prefixSum存储的是从根到root的父亲节点这一段所有的前缀和以及各自出现的次数
    private int dfs(TreeNode root, int curSum, int target, 
    			Map<Integer, Integer> prefixSum) {
        if (root == null) {
            return 0;
        }
        // 算一下从根到当前root的前缀和
        curSum += root.val;
        // 调取记忆,获取从根到当前节点这条路径上有多少个片段和为target
        // 此时获取的路径是包含当前节点的。本质上是在求以当前节点为结尾的,有多少条路径和为target
        int res = prefixSum.getOrDefault(curSum - target, 0);
        // 更新前缀和curSum的次数,表示又新遇到了一次前缀和curSum
        prefixSum.put(curSum, prefixSum.getOrDefault(curSum, 0) + 1);
        
        res += dfs(root.left, curSum, target, prefixSum);
        res += dfs(root.right, curSum, target, prefixSum);
        
        prefixSum.put(curSum, prefixSum.get(curSum) - 1);
        
        return res;
    }
}

时空复杂度 O ( n ) O(n) O(n)。空间复杂度是显然的。对于时间复杂度,只需要注意到每次到了某个节点的时候,只要 O ( 1 ) O(1) O(1)的时间取得以它为结尾的和等于sum的路径。

算法正确性证明:
只需证明dfs这个函数求的是,以root为根的子树的节点为终止节点,且和为target的路径总数。
还是用数学归纳法。当这个树是空树或者只有一个节点的时候,结论正确。容易证明,prefixSum这个哈希表里,在每次进入dfs这个函数前,存放的总是从树根到当前节点的所有前缀和以及它们出现的次数(递归调用结束的时候会把当前节点为止的前缀和减掉 1 1 1,保证这一条性质一直保持)。那么在dfs中,以当前节点为结尾的和为sum的路径个数即为prefixSum.getOrDefault(curSum - target, 0),由归纳假设,在执行完两次递归调用dfs后,res里已经存储了以当前节点为根节点的子树中的每一个节点为终止节点的所有和等于sum的路径个数。由数学归纳法,结论对更大规模的树成立。证明完毕。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值