前缀和
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[i] == arrsum[j] - 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。