前缀和的利用 前缀和的扩展问题
利用前缀和:快速找到所有满足要求的子数组数量&&变种问题
- 何为前缀和:对数组元素做累加和
- 如何用前缀和在一次O(N)找到所有符合要求的子数组数量?
- 步骤 1 :求出前缀和(见第一节)
- 步骤 2 :在前缀和上进行遍历,找到所有目标值
- 步骤 3 :循环次数优化
- 步骤 4 :初始值
- 实现
- 变种问题(同类问题,加一些帽子)
- 步骤 1 :转化问题
- 步骤 2 :套用上一题
- 步骤 3 :实现
何为前缀和:对数组元素做累加和
按照以下示例展示前缀和计算方法:
注意!!!!:前缀和数组第一个元素永远都是0,累加和从第二处开始添加
应用场景:求任意子数组 / 求数组内任意一个连续范围内 的和
初始状态:
1、nums = [1, 1, -1, 1, -1]
2、前缀和数组 s = [0, 0, 0, 0, 0, 0]。这是因为我们需要一个多一位的数组,其中 s[0] = 0 代表没有元素的前缀和。
计算前缀和数组 s:
迭代1:
nums[0] = 1,计算 s[[1]]= s[0] + 1 = 0 + 1 = 1,前缀和更新为 s = [0, 1, 0, 0, 0, 0]。
迭代2:
nums[[1]] = 1,计算 s[[2]] = s[[1]] + 1 = 1 + 1 = 2,前缀和更新为 s = [0, 1, 2, 0, 0, 0]。
迭代3:
nums[[2]] = -1,计算 s[[3]] = s[[2]] + (-1) = 2 - 1 = 1,前缀和更新为 s = [0, 1, 2, 1, 0, 0]。
迭代4:
nums[[3]] = 1,计算 s[[4]] = s[[3]] + 1 = 1 + 1 = 2,前缀和更新为 s = [0, 1, 2, 1, 2, 0]。
迭代5:
nums[[4]] = -1,计算 s[[5]] = s[[4]] + (-1) = 2 - 1 = 1,前缀和更新为 s = [0, 1, 2, 1, 2, 1]。
如何用前缀和在一次O(N)找到所有符合要求的子数组数量?
我们以 力扣560. 和为 K 的子数组 为例对该问题进行求解
步骤 1 :求出前缀和(见第一节)
步骤 2 :在前缀和上进行遍历,找到所有目标值
如何在前缀和上找到任意一个子数组的和:s[j] - s[i] = SUM
本题要求和为K,所以转化成:s[j] - s[i] = k
1、一个朴素的想法就是,枚举J的位置,再从0处枚举i,找到一个前缀和s[i]的值满足:s[i] = s[j] - k
2、但是显而易见的,复杂度=O(n^2),对于本题而言不可以,那么有没有什么办法可以省略掉第二次循环?
3、第二次循环的目的是什么?找到满足要求的所有s[i]的数量,所以我们动态维护就好了,采用一个字典维护出现过的所有前缀和的值以及他们的出现次数,这样就可以在O(1)的时间内得到满足要求的数量
4、这时,只需要遍历完所有元素,累加出的和即为答案。
步骤 3 :循环次数优化
很显然,对于每次的s[j]前面的元素,我已经不需要存储在数组中,只需要维护在HashMap中即可,我每次先更新此处的前缀和值,再根据s[i] = s[j] - k 找到s[i]的值,最后去HashMap中找到s[i]出现了几次,最后做累加即可。所以显而易见的,可以边算前缀和边统计
步骤 4 :初始值
满足上述计算条件,需要从nums[0]也就是s[1]开始,所以要把s[0]=0初始状态加到HashMap中,即初始值HashMap[0] = 1
实现
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int ans = 0;
unordered_map<int, int> cnt{ { 0,1 } };//注意写法
int s = 0;
for (int x : nums) {
s += x;//先计算此处前缀和的值
ans += cnt.find(s - k) == cnt.end() ? 0 : cnt[s - k];//在寻找s[i]出现几次,这里注意,不要直接调用cnt[s - k],因为没有cnt[s - k]的话会插入
cnt[s]++;//s出现次数多一次,在字典中为其更新
}
return ans;
}
};
变种问题(同类问题,加一些帽子)
我们以 力扣2588. 统计美丽子数组数目 为例对该问题进行求解
步骤 1 :转化问题
很明显的,从十进制看不出任何规律,并且,题目中明确提示了二进制,所以把例一转化一下,把每个数都写成二进制:
100
011
001
010
100
我们此时再看例一中两个符合要求的组,很明显的,以[4,3,1,2,4]为例,我们发现各个列都是2个1,另一个美丽子数组也同样。所以,很明显,体重操作就是执行异或操作,只要选择的数如上述摆放,每一列偶数个1就满足要求。
换句话说:满足要求的子数组,其所有元素的异或和 = 0
所以本题转化为:找到满足子数组所有元素异或和 = 0的子数组个数
步骤 2 :套用上一题
把上一题中 k 设置为 0 即可
+ 运算改为 ^ 即可
步骤 3 :实现
class Solution {
public:
long long beautifulSubarrays(vector<int>& nums) {
long long ans = 0;
unordered_map<int, int> cnt{{0, 1}};
int s = 0;
for (int x : nums) {
s ^= x;
ans += cnt[s - 0]++;
}
return ans;
}
};