前缀和
1、定义
前缀和的思路是这样的,对于一个给定的数组 nums,我们额外开辟一个前缀和数组进行预处理。
int n = nums.size();
// 前缀和数组
vector<int>preSum(n + 1);
for (int i = 0; i < n; i++)
preSum[i + 1] = preSum[i] + nums[i];
preSum[i]
就是 nums[0..i-1]
的和。那么如果我们想求 nums[i..j]
的和,只需要一步操作 preSum[j+1]-preSum[i]
即可,而不需要重新去遍历数组了。
2、题解:LeetCode560. 和为K的子数组
原题
给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。
输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。
说明 :
- 数组的长度为 [1, 20,000]。
- 数组中元素的范围是 [-1000, 1000] ,且整数 k 的范围是 [-1e7, 1e7]。
方法一:穷举所有子数组,计算子数组的和
关键是,如何快速得到某个子数组的和呢,比如说给你一个数组 nums
,让你实现一个接口 sum(i, j)
,这个接口要返回 nums[i..j]
的和,而且会被多次调用,你怎么实现这个接口呢?
因为接口要被多次调用,显然不能每次都去遍历 nums[i..j]
,有没有一种快速的方法在 O(1) 时间内算出 nums[i..j]
呢?这就需要前缀和技巧了。
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int len = nums.size();
vector<int> arrsum(len + 1);
for(int i = 0;i < len;i++)
{
arrsum[i+1] = arrsum[i] + nums[i];
}
int ans = 0;
for(int i = 1;i <= len;i++)
{
for(int j = 0; j < i;j++)
{
if(arrsum[i] - arrsum[j] == k)
ans++;
}
}
return ans;
}
};
问题:时间复杂度太高:超时
优化解法
for(int i = 1;i <= len;i++)
{
for(int j = 0; j < i;j++)
{
if(arrsum[i] - arrsum[j] == k)
ans++;
}
}
第二层 for 循环在干嘛呢?
- 有几个
j
能够使得sum[i]
和sum[j]
的差为 k
。毎找到一个这样的 j,就把结果加一。
arrsum[i] - arrsum[j] == k
arrsum[j] == arrsum[i] - k
优化的思路是:
直接记录下有几个 sum[j]
和 sum[i] - k 相等
,直接更新结果,就避免了内层的 for 循环。可以用哈希表
,在记录前缀和
的同时记录该前缀和出现的次数
。
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int sum = 0, ans = 0;
unordered_map<int,int> mp;
mp[0] = 1;
for(int i: nums){
sum += i;
if(mp.find(sum-k) != mp.end())
ans += mp[sum-k];
//把前缀和 nums[0..i] 加入并记录出现次数
mp[sum] ++;
}
return ans;
}
};
比如说下面这个情况,需要前缀和 8 就能找到和为 k 的子数组了,之前的暴力解法需要遍历数组去数有几个 8,而优化解法借助哈希表可以直接得知有几个前缀和为 8。
3、题解:LeetCode523. 连续的子数组和
原题链接
给定一个包含 非负数 的数组和一个目标 整数 k,编写一个函数来判断该数组是否含有连续的子数组,其大小至少为 2,且总和为 k 的倍数,即总和为 n*k,其中 n 也是一个整数。
示例 1:
输入:[23,2,4,6,7], k = 6
输出:True
解释:[2,4] 是一个大小为 2 的子数组,并且和为 6。
示例 2:
输入:[23,2,6,4,7], k = 6
输出:True
解释:[23,2,6,4,7]是大小为 5 的子数组,并且和为 42。
说明:
数组的长度不会超过 10,000 。
你可以认为所有数字总和在 32 位有符号整数范围内。
通过次数19,987提交次数89,540
class Solution {
public:
int checkSubarraySum(vector<int>& nums, int k) {
if (nums.empty()) {
return false;
}
vector<int> pre_sum(nums.size() + 1, 0);
for (int i = 0; i <nums.size(); ++i) {
pre_sum[i + 1] = nums[i] + pre_sum[i];
}
for (int i = 1; i <= nums.size(); ++i) {
for (int j = i; j <= nums.size(); ++j) {
if (k == 0 && (pre_sum[j] - pre_sum[i - 1]) == 0 && j - i + 1 >= 2) {
return true;
}
else if (k != 0 && (pre_sum[j] - pre_sum[i - 1]) % k == 0 && j - i + 1 >= 2) {
return true;
}
}
}
return false;
}
};
优化方案
1、空间换时间
map<int, int> mp;
key对应前缀和,value对应索引
2、公式转换
(arr[i] - arr[j])%k == 0
进一步得到
arr[i]%k == arr[j]%k
3、程序结果
class Solution {
public:
// 转换为求sum=0的情况
bool checkSubarraySum(vector<int>& nums, int k) {
map<int, int> mp;
mp[0] = -1;
int sum = 0;
for (int i = 0; i < nums.size(); i++) {
sum += nums[i];
if (k != 0) sum %= k;
if (mp.find(sum) != mp.end()) {
if (i - mp[sum] > 1) return true;
} else {
mp[sum] = i;
}
}
return false;
}
};