文章目录

  • 题目:437. 路径总和 III
  • 解题
  • 方式一:2次深度优先遍历
  • 方式二:深度优先遍历+前缀和


题目:437. 路径总和 III

给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。

路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

【NO.88】LeetCode HOT 100—437. 路径总和 III_leetcode

示例 1:

输入:root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
输出:3
解释:和等于 8 的路径有 3 条,如图所示。
示例 2:

输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:3

提示:

二叉树的节点个数的范围是 [0,1000]

-109 <= Node.val <= 109

-1000 <= targetSum <= 1000

解题

方式一:2次深度优先遍历

首先想到的解法是穷举所有的可能,我们访问每一个节点 node,检测以 node为起始节点且向下延深的路径有多少种。我们递归遍历每一个节点的所有可能的路径,然后将这些路径数目加起来即为返回结果。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    // 从每一个结点开始往下遍历,直到最后的叶子节点,路径和达到targetSum就数组加1
    // 两次循环遍历,时间复杂度O(n^2),空间复杂度为O(N)递归需要在栈上开辟空间
    // 二叉树循环遍历优先考虑深度优先遍历
    public int pathSum(TreeNode root, int targetSum) {
        
        if (root == null) {
            return 0;
        }
        // 计算从当前节点开始满足条件的数目
        int result = getPathNum(root, targetSum);

        // 第一层遍历,从每个节点开始计算路径和
        result += pathSum(root.left, targetSum);
        result += pathSum(root.right, targetSum);

        // 两层遍历结束,返回
        return result;
    }


    //第二层循环遍历:计算从节点node开始到最后叶子节点的所有路径,结点值和为targetSum的条数
    private int getPathNum(TreeNode node, long targetSum) {
        if (node == null) {
            return 0;
        }
        int result = 0;
        if (node.val == targetSum) {
           result++;
        }
        result += getPathNum(node.left, targetSum - node.val);
        result += getPathNum(node.right, targetSum - node.val);

        return result;
    }
}

方式二:深度优先遍历+前缀和

两层循环会有很多重复计算,考虑如何利用已经计算过的信息。类似在数组中找连续子数组和为targetSum,本题也在父子链路上体现出从某点到某点的连续性。这启发我们使用前缀和方法处理。

使用dfs遍历一次整棵树,实时地计算从root到当前结点的前缀和。在同一条路径上,更短的前缀和已经被计算出来了,为了对前缀和求差以查找是否存在pre_i - pre_j = targetSum(pre_i为当前结点前缀和,pre_j为当前路径上i节点之前的结点j的前缀和),我们需要保存之前结点的前缀和。map是一个不假思索的选择,key保存前缀和,value保存对应此前缀和的数量。

需要注意的是,前缀和求差的对象是同一条路径上的结点,因此在dfs遍历树的过程中,当到达叶子结点,之后向上返回时,路径退缩,使得当前结点将退出后续路径【对前缀和求差的前提是要保证map中所保存的前缀和均为同一路径上的结点的前缀和,因此需要删除返回前的节点所代表的前缀和】

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    // 时间复杂度为O(N),空间复杂度为O(N)
    public int pathSum(TreeNode root, int targetSum) {
        if (root == null) {
            return 0;
        }
        // map key保存前缀和,value保存对应此前缀和的数量
        HashMap<Long, Integer> preNumMap = new HashMap<>();
        // 初始化 前缀和为0的情况
        preNumMap.put(0L, 1);
        // 开启遍历
        return dfs(root, preNumMap, 0L, targetSum);
    }

    // curSum 为从根节点到当前节点的和
    private int dfs(TreeNode root, HashMap<Long, Integer> preNumMap, Long curSum, int targetSum) {
        if (root == null) {
            return 0;
        }
        // 初始化结果
        int ret = 0;
        // 计算当前和
        curSum += root.val;

        // 从缓存中查询符合条件的数目
        ret += preNumMap.getOrDefault(curSum - targetSum, 0);

        // 将当前路径和放进去
        preNumMap.put(curSum, preNumMap.getOrDefault(curSum, 0)+1);
        // 遍历左右孩子
        ret += dfs(root.left, preNumMap, curSum, targetSum);
        ret += dfs(root.right, preNumMap, curSum, targetSum);

        // 退出时,map中删除当前节点
        preNumMap.put(curSum, preNumMap.getOrDefault(curSum, 0)-1);

        return ret;
    }
}