Leetcode第862题:和至少为K的最短子数组(Shortest Subarray with sum at least k)
解题思路
前缀和
定义前缀和\(s[0] = 0\), \(s[i+1] = \displaystyle\sum\limits_{j=0}^i nums[j]\)。
例如 \(nums=[1,2,-1,2]\),对应的前缀和数组为\(s=[0,1,3,2,4]\)。
通过前缀和,可以将子数组和转换为两个前缀和的差,即
\(\sum\limits_{j=left}^{right} nums[j] = \sum\limits_{j=0}^{right} nums[j] - \sum\limits_{j=0}^{left-1} nums[j] = s[right+1] - s[left]\)
例如\(nums\)的子数组\([2,-1,2]\)的和就可以用\(s[4] - s[1] = 4 -1 = 3\)计算出来.
此时就可以通过枚举所有\(i>j\)且\(s[i] - s[j] \geq k\)的子数组\([j,i)\),取其中最小的\(i-j\)作为答案.
单调队列
枚举所有前缀和子数组的时间复杂度是\(O(n^2)\),使用某个数据结构维护遍历过的\(s[i]\),及时移除无用的\(s[i]\).
第一,当遍历到\(s[i]\)时,如果左边存在某个\(s[j]\),满足\(s[i] - s[j] \geq k\),那么无论\(s[i]\)右边的数字大小,都不会把\(j\)当作子数组的左端点,然后得到一个比\(i - j\)更短的子数组.因此可以将\(s[j]\)移除.
第二,如果\(s[i] \leq s[j]\),假如后续有数字\(x\)能和\(s[j]\)组成满足要求的子数组,即\(x - s[j] \geq k\),那么必然也有\(x - s[i] \geq k\),由于从\(s[i]\)到\(x\)的子数组更短,因此可以将\(s[j]\)移除.
做完这两个优化后,再把\(s[i]\)加到这个数据结构中.第二个优化保证了数据结构中的\(s[i]\)会形成一个递增的序列,因此第一个优化移除的是序列最左侧的若干元素,第二个优化移除的是最右侧的若干元素.因此该数据结构满足移除最左端元素和最右端元素,以及在最右端添加元素,故选用双端队列.元素保持单调递增的双端队列也成为单调队列.
核心代码如下:
int shortestSubarray(vector<int> &nums, int k) {
int n = nums.size(), ans = n + 1;
long s[n + 1];
s[0] = 0L;
for (int i = 0; i < n; ++i)
s[i + 1] = s[i] + nums[i]; // 计算前缀和
deque<int> q;
for (int i = 0; i <= n; ++i) {
long cur_s = s[i];
while (!q.empty() && cur_s - s[q.front()] >= k) {
ans = min(ans, i - q.front());
q.pop_front(); // 优化一
}
while (!q.empty() && s[q.back()] >= cur_s)
q.pop_back(); // 优化二
q.push_back(i);
}
return ans > n ? -1 : ans;
}

浙公网安备 33010602011771号