本题的题目让新手(比如说我)很容易想到爆搜,两个for循环直接遍历每个子数组,可以很容易得到答案,但是本题作为困难题,难点就是范围较大,用O(n²)的算法必然会爆,我们就要换个思路,我用的方法是前缀和+ 双向队列。
为什么我会想到用前缀和,本题是连续的子数组,并且要求和,连续+求和让我想到了用前缀和,可能有些人不知道啥叫前缀和,即创建一个数组,假设数组为S[n],原数组为A[n],前缀和数组就是使得s[n] = a[0] + a[1] +a[2] ... + a[n],即前n个元素的和。
何为双向队列,即队头和队尾都可以执行插入和删除操作,不像普通队列只能在队尾插入,队头删除。
本题还有一个关键的点,就是答案的子数组长度怎么来的,我在这举个例子
我们可以看出来,在前缀和数组中满足S[n] - S[m] >= k,(n > m) ,即可满足从A[m] 到 A[n]的子数组的和大于等于k,满足题意,那我们怎么找到最小的子数组呢,我们要进行减枝。
比如说上图的S[4] - S[1]是大于K的,S[4] - S[2]也是大于K的,A[1] < 0, 我们可以得出一个结论,如果说S[m] < S[n],(m > n),我们可以略过S[m]这个元素,因为后面的元素必然比S[m]大而且子数组的长度也比S[m]到另一端的距离更短(此处的另一端为S[4]),不难看出只要A[n] < 0,我们可以略过相应的前缀和数组往下搜索。
还是这个例子,我们找到了S[4] - S[1] >= k,我们接下来为了找到更短的,应该调整S[1]的位置,让他往后移动,更加靠近S[4],才能找到最小的,直到栈空
class Solution {
public:
int shortestSubarray(vector<int>& nums, int k)
{
int n = nums.size();
int ans = n + 1;
vector<long> temp;
long sum = 0;
temp.push_back(0);
for(int & num : nums)
{
sum += num;
temp.push_back(sum);
}
deque<int> deq;
for(int i = 0;i < n + 1; i ++)
{
while(!deq.empty() && temp[i] <= temp[deq.back()])
{
deq.pop_back();
}
while(!deq.empty() && temp[i] - temp[deq.front()] >= k)
{
int temp1 = i - deq.front();
deq.pop_front();
ans = min (ans,temp1);
}
deq.push_back(i);
}
return ans == n + 1? -1 : ans;
}
};