34. 二叉树中和为某一值的路径
1 题目描述
输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。
示例:给定如下二叉树,以及目标target=22,
5
/ \
4 8
/ / \
11 13 4
/ \ / \
7 2 5 1
返回
[
[5,4,11,2],
[5,8,4,5]
]
2 题目分析
根据题意,我们要返回每一条从根节点到叶子节点和为target的路径,很容易想到dfs和双端队列。对于当前节点,先用target减去当前节点的值,将当前节点加入到路径中,然后递归判断其左子树和右子树是否有和为target-当前值的路径。碰到叶子节点,将叶子节点弹出。
3 代码
List<List<Integer>> ans = new LinkedList<>();
// 定义一个栈用来弹出叶子节点操作
Deque<Integer> path = new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int target) {
dfs(root, target);
return ans;
}
private void dfs(TreeNode root, int target) {
if (root == null) return;
// 由于是打印从根节点的路径,因此不能用栈的属性,push方法是往双端队列首部添加,我们要往尾部添加
// 因此要用add或者offer或者offerLast
path.offerLast(root.val);
target -= root.val;
// 判断是否是叶子节点
if (root.left == null && root.right == null && target == 0) {
// 找到一条路径
ans.add(new LinkedList<Integer>(path));
}
dfs(root.left, target);
dfs(root.right, target);
// 将双端队列的尾部元素移除
path.pollLast();
}
4 扩展:(力扣第437题)
4.1 前缀和概念
不了解前缀和的先看一下前缀和的概念前缀和,这篇文章中介绍了一维和二维情况下的前缀和的应用场景,而下面这道题离谱到了对二叉树的前缀和使用。
4.2 题目描述
给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
示例:
输入:root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
输出:3
解释:和等于 8 的路径有 3 条,如图所示。
4.3 题目分析
创建二叉树的前缀和形式如下图所示:
从上图中我们就可以轻松知道一条路径中和为target的子路径有多少个,只需在前缀和二叉树中找当前的前缀和curSum-target出现的个数num即可,这个num就是以当前节点作为子序列的最后一个元素的和为target的路径个数。
注意上面我们要找的是个数,相信到这里很多同学都会想到哈希表的数据结构,是的,对于节点我们用HashMap去存储它本身和出现的次数;而我们是用递归的思想去实现的,因此当当前节点左右子树都遍历完后需要在哈希表中删除当前节点(即对应的value减1),思路有了代码就很容易了。
4.4 代码
public int pathSum(TreeNode root, int targetSum) {
if (root == null) return 0;
// 初始化前缀和
Map<Integer, Integer> prefixSumCount = new HashMap<>();
prefixSumCount.put(0, 1);
return recursionPathSum(root, prefixSumCount, targetSum, 0);
}
/*
递归构建前缀和
*/
private int recursionPathSum(TreeNode root, Map<Integer, Integer> prefixSumCount, int targetSum, int currSum) {
// 递归出口
if (root == null) return 0;
int count = 0; // 初始化个数
currSum += root.val; // 计算当前节点的前缀和
// 判断currSum-targetSum在map中出现的次数,更新count
count += prefixSumCount.getOrDefault(currSum - targetSum, 0);
// 更新map
prefixSumCount.put(currSum, prefixSumCount.getOrDefault(currSum, 0) + 1);
// 遍历左右子树
count += recursionPathSum(root.left, prefixSumCount, targetSum, currSum);
count += recursionPathSum(root.right, prefixSumCount, targetSum, currSum);
// 回溯
prefixSumCount.put(currSum, prefixSumCount.get(currSum) - 1);
return count;
}