前缀和
通常,涉及连续子数组问题的时候,我们使用前缀和来解决
就像看见时间复杂度log开头就想到二分查找和数,最值问题不是贪婪就是动态规划,
字符串或者数字之间求交集(在字符串中是子串),或者符合要求的连续子集或者子字符串,当然滑动窗口。
看到子数组和,有必要马上想到前缀和
(切记是子数组和,是连续的)
关于前缀和:从第0项算起,到第i项的和。
前缀和完整表达式:
某项值:
叠加多项:
同时,前缀和常与hash表同时出现,下面我们看看如何使用。
一般套路:
下面我们看看前缀和的题目具体怎么处理:
560. 和为K的子数组
https://leetcode-cn.com/problems/subarray-sum-equals-k/
参考题解:
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
unordered_map<int, int> mp;
mp[0] = 1;
int count = 0, pre = 0;
for (auto& x:nums) {
pre += x;
if (mp.find(pre - k) != mp.end()) count += mp[pre - k];
mp[pre]++;
}
return count;
}
};
// 作者:LeetCode-Solution
// 链接:https://leetcode-cn.com/problems/subarray-sum-equals-k/solution/he-wei-kde-zi-shu-zu-by-leetcode-solution/
// 来源:力扣(LeetCode)
// 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
将条件,转换为在Hash表中索引的关键信息:
所以程序才这么写:
if (mp.find(pre - k) != mp.end()) count += mp[pre - k];
因为Hash表的意义就是统计前缀和出现的次数,这个Hash的意义,是根据条件方程得出的,条件方程的右侧是前缀和,所以Hash表的键值也是前缀和,所以此时有:
mp[pre]++;
974. 和可被 K 整除的子数组
https://leetcode-cn.com/problems/subarray-sums-divisible-by-k/
class Solution {
public:
int subarraysDivByK(vector<int>& A, int K) {
unordered_map<int,int>M;
M[0] = 1;
int sum = 0,cout = 0;
for(auto item:A)
{
sum += item;
int modulus = (sum % K + K) % K;
if(M[modulus]) cout += M[modulus];
M[modulus]++;
}
return cout;
}
};
此题和560非常相似:
有条件方程:
那么我们还是一样的做法:
让Hash表键值为前缀和的余数(因为条件方程右侧是余数),Hash的value就是该余数出现的次数,有核心代码如下:
int modulus = (sum % K + K) % K;
if(M[modulus]) cout += M[modulus];
M[modulus]++;
1588. 所有奇数长度子数组的和 https://leetcode-cn.com/problems/sum-of-all-odd-length-subarrays/
int sumOddLengthSubarrays(int* arr, int arrSize){
if (arr == NULL) {
return 0;
}
int preSum[arrSize + 1];// 前缀和
preSum[0] = 0;
preSum[1] = arr[0];
for (int i = 1; i < arrSize; ++i) {
preSum[i + 1] = preSum[i] + arr[i];
}
int sum = 0;
int range = 1;
while (range <= arrSize) {
int index = range;
for (;index <= arrSize;++index) {
sum += preSum[index] - preSum[index - range];
printf("size = %d, sum = %d\n", range, sum);
}
range += 2;
}
return sum;
}