文章目录
- 题目:437. 路径总和 III
- 解题
- 方式一:2次深度优先遍历
- 方式二:深度优先遍历+前缀和
题目:437. 路径总和 III
给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。
路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
示例 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;
}
}