滑动窗口
滑动窗口遇到的最大的问题就是,在本题中出现负数,打破了滑动窗口中sum单调递增,因此会遇到很多的问题。
这里在维护窗口的同时,并修改在窗口里的值把nums[left] nums[right]
中的值将负值nums[i]
通过与左边的数相加放到左侧,来最终得到一个大于等于0的数(如果到了最左端还是小于0,那么就需要把这个负数抛弃掉),来放到最左侧来维护滑动窗口中的递增性。
可以把负值一直与左侧相加得到新的左侧的基础是,如果最后负数不能通过与左侧的数相加成为一个正数,则证明在nums[left] nums[right]
中nums[left] 到 nums[i]
结果为负,如果此时sum >= k
的话nums[i] 到 nums[right]
一定sum >= k
且比nums[left] nums[right]
长度短
换而言之,对于找到最短的子数组来说,负数的出现,影响的是负数左边最近的正数,如果这个正数不在结果的窗口内,那么这些负数一定不在窗口内
class Solution {
public:
int shortestSubarray(vector<int>& nums, int k) {
int sum = 0;
int left = 0;
int res = nums.size() + 1;
for(int right = 0; right < nums.size(); right++)
{
if(nums[right] >= k)
return 1;
sum += nums[right];
if(sum <= 0)
{
left = right + 1;
sum = 0;
continue;
}
for(int j = right - 1; nums[j + 1] < 0; j--)
{
nums[j] += nums[j + 1];
nums[j + 1] = 0;
}
if(sum < k)
continue;
while(sum - nums[left] >= k)
sum -= nums[left++];
res = min(res, right - left + 1);
}
return res == nums.size() + 1 ? -1 : res;
}
};
双端队列
比较好理解了,对于新的要加入队列的数sum
来说,有两种情况,
一种是sum - 队列最前面的和 >= k
,这种情况就可以不用保留当前队列最前面的和了,因为它本身和他前面的数与sum相减一定是>= k
的。因此需要弹出此时队列最前面的和
并重复操作
一种是sum [i] < 队列最后面的和 [j]
,由于此时 队列最后面的和 [j]
在sum
之前出现,因此如果某个 >=k
的子串j - k
从此时 队列最后面的和 [j]
开始,则从sum[i]
开始一定的子串i - k
一定>=k
且比
j - k
短,因此需要弹出队列最后面的和 [j]
并重复
class Solution {
public:
int shortestSubarray(vector<int>& nums, int k) {
deque<pair<long, int>> que;
int res = nums.size() + 1;
long sum = 0;
que.push_back(pair<long, int>(sum, 0));
for(int i = 0; i < nums.size(); i++)
{
sum += nums[i];
while(!que.empty() && sum - que.front().first >= k)
{
res = min(res, i - que.front().second + 1);
que.pop_front();
}
while(!que.empty() && sum <= que.back().first)
que.pop_back();
que.push_back(pair<long, int>(sum, i + 1));
}
return res == nums.size() + 1 ? -1 : res;
}
};