和至少为 K 的最短子数组
里面的元素可以为负数,故不可以用滑动窗口
涉及知识:
-
前缀和数组:
sum[i] = array[0] + array[1] + ... + array[i-1]
若要求区间[3, 5]的和,则可用sum[6] - sum[3]
(注意,为了计算方便,加了sum[0] = 0
,即前缀和数组长度为数组长度+1) -
单调双端队列:考虑当前位置i,这时
[0, i]
的值比区间[0, i-1]
还要小,则对于后面的位置,就不需要考虑以i-1为起始的区间了,因为sum[n] - sum[i] > sum[n] - sum[i-1]
,且前者区间长度更短。故可以把i-1
踢出队列。
解法: 从队列中最小的位置开始计算,如果符合条件,就记录答案,并将这个位置踢出队列,因为再往后不会有比这个还要小的符合条件的长度。
class Solution{
public:
int shortestSubarray(vector<int>& nums, int k) {
deque<int> dq; //双端队列
int min_len = INT_MAX;
vector<int> sum(nums.size()+1, 0); //长度+1
//获取前缀和
for(int i = 1; i <= nums.size(); i++){
sum[i] = sum[i-1] + nums[i-1];
}
for(int i = 0; i < nums.size(); i++){
while(dq.size() > 0 && sum[dq.back()] >= sum[i]) //保证deque里面的sum是严格递增的 我们确定我们前面没有高个,有就踢他
dq.pop_back();
while(dq.size() > 0 && sum[i] - sum[dq.front()] >= k){ 从最前面第一位同学开始,进行比较
min_len = min(min_len, i - dq.front());
dq.pop_front();
}
dq.push_back(i); 最后才坐下去
}
return min_len == INT_MAX? -1:min_len;
}
};
故题目意思为有几种i、j的组合,满足prefixSum[j] - prefixSum[i-1] = k;
- 每个元素对应一个“前缀和”
- 遍历数组,根据当前“前缀和”,在 map 中寻找「与之相减 == k」的历史前缀和
- 当前“前缀和”与历史前缀和,差分出一个子数组,该历史前缀和出现过 c 次,等价于当前项找到 c 个子数组求和等于 k。
- 遍历过程中,c 不断加给 count,最后返回 count
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
unordered_map<int, int> mp;
mp[0] = 1; //即当i = -1时,prefixSum[-1] = 0;
int cnt = 0, prefixSum = 0;
for(int i = 0; i < nums.size(); i++){
prefixSum += nums[i];
if(mp[prefixSum - k]){
cnt += mp[prefixSum - k];
}
if(mp[prefixSum])
mp[prefixSum]++;
else
mp[prefixSum] = 1;
}
return cnt;
}
};
class Solution {
public:
int findMaxLength(vector<int>& nums) {
unordered_map<int, int> mp;
mp[0] = -1;
int max_len = 0;
int sum = 0;
for(int j = 0; j < nums.size(); j++)
if(nums[j] == 0)
nums[j] = -1;
for(int i = 0; i < nums.size(); i++){
sum = sum + nums[i];
if(mp.count(sum) != 0){
int len = i - mp[sum];
if(len > max_len)
max_len = len;
//mp[sum] = i;
}
else{
mp[sum] = i;
}
}
return max_len;
}
};