LeetCode 17 - 前缀和
437 路径总和 III#
给定一个二叉树的根节点 root
,和一个整数 targetSum
,求该二叉树里节点值之和等于 targetSum
的 路径 的数目。路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
方法一:DFS
我们利用 DFS 穷举所有可能,访问每一个结点 node:
- 以 node 为起点,检测以它为根向下延伸的满足条件的路径条数。在此过程中,首先看结点 node 的值是否已经等于 targetSum,是则合法路径加一;然后以 node 为路径起点继续向下搜索。
- 计算 node 的左右子树中满足条件的路径数。(对左右子树递归调用)
- 将每个结点的合法路径条数相加即为答案。
这个 DFS 涉及到了两层递归:
- 在主方法里面有递归调用:对当前结点计算后,需要对左右孩子递归计算。
- 在 DFS 方法里面也有递归调用:确定了以当前节点为路径起点后,需要向下延伸,对左右孩子递归计算。
int pathSum(TreeNode root, int targetSum) {
if(root == null) return 0;
// 计算 **以当前节点为起点** 且满足条件的路径条数
int result = findPathNum(root, targetSum);
// 递归计算 **左右子树** 中满足条件的路径条数
result += pathSum(root.left, targetSum);
result += pathSum(root.right, targetSum);
return result;
}
// 以 node **为起点** 的满足条件的路径条数
int findPathNum(TreeNode node, int targetSum) {
if(root == null) return 0;
int result = 0;
int curVal = node.val;
// 当前节点可以自成一条路径
if(curVal == targetSum) result++;
// 以当前节点为起点,向下延伸的满足条件的路径条数
result += findPathSum(root.left, targetSum - curVal);
result += findPathSum(root.right, targetSum - curVal);
return result;
}
方法二:前缀和
方法一中存在很多重复计算,我们可以将已计算过的路径和缓存起来,这就用到了前缀和的概念:结点的前缀和定义为 从根结点到当前节点的路径上所有结点的和(包括当前结点)。
注意前缀和的一个特性:在一条路径上,如果两个点的前缀和相同,则这两个点之间的元素总和为 0。进一步,如果 A 和 B 在同一条路径上,A 在 B 之前,且 A 和 B 的前缀和分别是 sum - target
和 sum
,那么从 A.next 到 B 的路径和为 target
。
// key 是前缀和,value 是对应前缀和出现的次数
HashMap<Integer, Integer> sumCount = new HashMap<>();
int pathSum(TreeNode root, int targetSum) {
sumCount.put(0, 1);
return dfs(root, targetSum, 0);
}
// 计算从根结点到每一个结点的前缀和
// 对遍历到的每个点计算以它为终点的满足条件的路径数
int dfs(TreeNode node, int target, int curSum) {
if(node == null) return 0;
int result = 0;
// 更新前缀和
curSum += node.val;
// 寻找当前这条路径上前缀和为 curSum-target 的结点数量
result += sumCount.getOrDefault(curSum - target, 0);
// 更新路径上当前前缀和的结点个数
// 更新操作必须在上一行的获取操作之后
sumCount.put(curSum, sumCount.getOrDefault(curSum, 0) + 1);
// 进入下一层
result += dfs(node.left, target, curSum);
result += dfs(node.right, target, curSum);
// 以当前结点为根的子树统计完毕,
// 去除当前节点的前缀和对应节点的数量
sumCount.put(curSum, sumCount.get(curSum) - 1);
return result;
}
560 和为 K 的子数组#
给你一个整数数组 nums
和一个整数 k
,请你统计并返回 该数组中和为 k
的子数组的个数 。
方法:前缀和
定义前缀和数组 sum
,其中 sum[i]
表示子数组 [0...i]
中所有数的和。那么「[j..i]
这个子数组的和为 k 」 这个条件可以转化为:。那么遍历到当前位置 i
并计算出 sum[i]
后,只需要检查满足前缀和为 sum[i]-k
的数有多少个即可。
int subarraySum(int[] nums, int k) {
int count = 0, sum = 0;
HashMap<Integer, Integer> sumCount = new HashMap<>();
sumCount.put(0, 1); // 这是为 sum[i]==k 这种情况准备的
for(int i = 0; i < nums.length; i++) {
sum += nums[i]; // 计算当前位置前缀和
if(sumCount.containsKey(sum-k))
count += sumCount.get(sum-k);
sumCount.put(sum, sumCount.getOrDefault(sum, 0) + 1);
}
return count;
}
上面的 sumCount.put(0, 1)
是为了统计 sum[i]
刚好等于 k
这种情况,因为此时 sum[i]-k
刚好就等于 0
。
1248 统计优美子数组#
给你一个整数数组 nums
和一个整数 k。如果某个连续子数组中 恰好有 k 个奇数数字,我们就认为这个子数组是「优美子数组」。请返回这个数组中 「优美子数组」 的数目。
方法:前缀和
用 oddCount[i]
表示子数组 [0..i]
中奇数的个数,则有:
那么「[j..i]
这个子数组里的奇数个数恰好为 k 」这个条件可以转化为:
即下标 j
需要满足:
所以统计以 i
结尾的优美子数组时只要统计有多少个奇数个数前缀和为 oddCount[i]-k
的位置即可。且因为每个位置之和前一个位置有关,所以不必真地创建一个数组,而只需要使用两个变量即可。
int numOfSubarrays(int[] nums, int k) {
int n = nums.length;
// prefix[oddCount] 表示前缀奇数个数为 oddCount 的位置有几个
int[] prefix = new int[n + 1];
int oddSum = 0, result = 0;
prefix[0] = 1; // 表示 prefix[oddCount] == k 的情况
for (int i = 0; i < n; i++) {
oddSum += nums[i] & 1;
result += (oddSum >= k ? prefix[oddSum - k] : 0);
prefix[oddSum] += 1;
}
return result;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix