目录
前缀和概念
对于一个序列,其前缀和是一个数组,其长度等于序列的长度。前缀和数组中每个元素sum[i]都是原数组arr中前i个元素的和。比如:
arr: 0 1 2 3 4 5
sum:0 1 3 6 10 15
通过两层循环,让sum[i] - sum[j] 就可以得到原数组中所有子数组内元素的和。注意对于从开头开始的子数组直接用sum[i]就能搞定,根本不需要减。
接下来看几个例子
例子1:leetcode 560 和为k的子数组
https://leetcode-cn.com/problems/subarray-sum-equals-k/
第一种解法,即原始解法,用双层循环来找到符合条件的子数组
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int len = nums.size();
int count = 0;
int sum[len];
// 计算前缀和数组
sum[0] = nums[0];
for(int i = 1; i < len; ++i){
sum[i] = sum[i-1] + nums[i];
}
// 用两层循环就可以遍历所有子数组的情况
for(int i = 0; i < len; ++i){
for(int j = -1; j < i; ++j){
// j从-1开始是因为从零位置开始的子数组不需要作差就能得到
int sumofsub = 0; // 子数组中元素和可以由前缀和数组作差得到
if(j < 0) sumofsub = sum[i];
else sumofsub = sum[i] - sum[j];
if(sumofsub == k) count++;
}
}
return count;
}
};
上边我们用两层循环来穷举所有的子数组,但是实际上第二层是没有必要的,我们只需要用一层循环遍历前缀和数组,然后查找前缀和数组中有没有一个元素减去当前元素等于k。
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int len = nums.size();
int count = 0;
int sum[len];
// 计算前缀和数组
sum[0] = nums[0];
for(int i = 1; i < len; ++i){
sum[i] = sum[i-1] + nums[i];
}
unordered_map<int, int> mp;
mp[0] = 1; // 代表不需要作差的情况
// 前缀和加哈希查找
for(int i = 0; i < len; ++i){
if(mp.find(sum[i] - k) != mp.end()) {
count += mp[sum[i] - k];
}
mp[sum[i]]++;
}
return count;
}
};
例子2 NC125 累加和为定值得最长子数组
class Solution {
public:
/**
* max length of the subarray sum = k
* @param arr int整型vector the array
* @param k int整型 target
* @return int整型
*/
int maxlenEqualK(vector<int>& arr, int k) {
// write code here
int n = arr.size(), res = 0;
// 计算前缀和数组
vector<int> preSum(n);
preSum[0] = arr[0];
for (int i = 1; i < n; i ++) {
preSum[i] = preSum[i - 1] + arr[i];
}
// 哈希加速查找,键是前缀和数组元素,值是前缀和数组索引
unordered_map<int, int> hash;
hash[0] = -1; // 代表从开头开始的子数组
for (int i = 0; i < n; i ++) {
int tmp = -k + preSum[i];
if (hash.find(tmp) != hash.end())
res = max(res, i - hash[tmp]);
if (hash.find(preSum[i]) == hash.end())
hash[preSum[i]] = i;
}
return res;
}
};
注意,刚开始的时候我以为 i - hash[tmp]这里需要再加1,但实际上是不同的,前缀和数组中某两个相邻元素相减等于一个给定值,意味着在原数组中就存在一个这样的值,即在原数组中的长度为1。