和至少为 K 的最短子数组

返回 A 的最短的非空连续子数组的长度,该子数组的和至少为 K 。
如果没有和至少为 K 的非空子数组,返回 -1 。

示例 1:
输入:A = [1], K = 1
输出:1

一开始想的很好,直接暴力循环嘛,然而这当然是不能通过全解的,这是困难难度,不是中等难度。
果然,最后一个样例,A有十万个数字,暴力循环直接超时。
看官方解,思路如下:

我们用数组 P 表示数组 A 的前缀和,即 P[i] = A[0] + A[1] + ... + A[i - 1]。我们需要找到 x 和 y,使得 P[y] - P[x] >= K 且 y - x 最小。

这一步大家都理解,无非是避免重复计算,优化算法。

我们用 opt(y) 表示对于固定的 y,最大的满足 P[x] <= P[y] - K 的 x,这样所有 y - opt(y) 中的最小值即为答案。

第一步得到了前缀和数组P,通过对于P的遍历,我们可以找到答案。到这一步都还好理解,但是仅仅如此优化的程度还不够,还是暴力循环的路子。

 1. 如果 x1 < x2 且 P[x2] <= P[x1],那么 opt(y) 的值不可能为 x1,这是因为 x2 比 x1 大,并且如果 x1 满足了 P[x1] <= P[y] - K,那么 P[x2] <= P[x1] <= P[y] - K,即 x2 同样满足 P[x2]<= P[y] - K。
 2. 如果 opt(y1) 的值为 x,那么我们以后就不用再考虑 x 了。这是因为如果有 y2 > y1 且 opt(y2) 的值也为 x,但此时y2 - x 显然大于 y1 - x,不会作为所有 y - opt(y) 中的最小值。

这是此题的两条性质,都还好理解。它们的作用是舍弃不必要的计算,来达到优化时间复杂度的作用。


性质一举例: 若P[1]=2,P[2]=5,而P[3]=3,显然题目给出的数组A中,A[0]=2,A[1]=3,A[2]=-2
那么在考虑元素坐标y=6时,寻找opt(y)即最大的满足 P[x] <= P[y] - K 的 x时,我们可以直接略过2,遍历顺序为P[1],[3]........这就是利用性质一来略去不必要的计算,实质则是利用前缀和的性质直接略去A中值为负数的坐标。但这不是简单的略去,我们略去的只是该坐标的前缀和,这个为负数的元素仍然存在于坐标位于它之后的前缀和中,例如上面提到的A[2]值为-2,我们舍去了P[2],但A[2]仍然被包含于P[3]中。

 我们维护一个关于前缀和数组 P 的单调队列C,它是一个双端队列(deque),其中存放了下标 x:x0, x1, ... 满足 P[x0], P[x1], ... 单调递增。这是为了满足性质一。

 当我们遇到了一个新的下标 y 时,我们会在C队尾移除若干元素,直到 P[x0],P[x1], ..., P[y] 单调递增。这同样是为了满足性质一。

同时,我们会在C队首也移除若干元素,如果 P[y] >= P[x0] + K,则将C队首元素移除,直到该不等式不满足。这是为了满足性质二。

注意,为了便于理解有修改。

这是这道题的精华之处,首先看第一条,之所以要有双端队列C,是为了便于两端pop元素。而之所以需要在两端pop元素,是为了略去不必要的计算(之所以能略去则是由于本题的性质所决定,上面已经提到)。

我们算法的所有操作,都在双端队列C上进行,前缀和数组P只是一个提供基础数据的作用。需要注意的是,双端队列中保存的x1,x2,x3指的是原题中给出的数组A的元素坐标,而非前缀和。

leetcode这串话,其实第一段和第二段是一个意思,即利用性质一使双端队列C单调递增,pop掉C队尾的不能使单调递增性质成立的前缀和元素,至于为什么上面已经提到。
性质二很简单,我就不详细说了。
代码如下:



 1 int n = A.size();//这里需要注意一下,最好将A.size()转化为int,因为A.size()其实是自然数,后续要是有A.size()-7之类的会发生下溢
 2 vector<int> p(n + 1, 0);
 3 for (int i = 0; i < n; ++i)
 4 {
 5 p[i + 1] = p[i] + A[i];
 6 }//生成前缀和数组p
 7 deque<int> c;//定义双端队列c
 8 int ans = n + 1;
 9 for (int y = 0; y <= n; ++y)//其实就是对前缀和队列p进行遍历
10 {
11 while (!c.empty() && p[y] <= p[c.back()])//性质一
12 {
13 c.pop_back();
14 }
15 while (!c.empty() && p[y] - p[c.front()] >= K)//性质二
16 {
17 ans = min(ans, y - c.front());//寻找y-opt(y)最小值
18 c.pop_front();
19 }
20 c.push_back(y);
21 
22 }
23 return ans <= n ? ans : -1;


参考:
https://leetcode-cn.com/problems/shortest-subarray-with-sum-at-least-k/solution/he-zhi-shao-wei-k-de-zui-duan-zi-shu-zu-by-leetcod/

原文地址:https://www.cnblogs.com/BYGAO/p/12331576.html