版权声明:本文为博主原创文章,博客地址:https://blog.csdn.net/qq_41855420,未经博主允许不得转载。 https://blog.csdn.net/qq_41855420/article/details/91128600
返回 A 的最短的非空连续子数组的长度,该子数组的和至少为 K 。
如果没有和至少为 K 的非空子数组,返回 -1 。
示例 1:
输入:A = [1], K = 1
输出:1
示例 2:
输入:A = [1,2], K = 4
输出:-1
示例 3:
输入:A = [2,-1,2], K = 3
输出:3
提示:
1 <= A.length <= 50000
-10 ^ 5 <= A[i] <= 10 ^ 5
1 <= K <= 10 ^ 9
这道题测试数据量比较大,所以使用蛮力法的都会超时。下面的代码是来自评论区大神的O(n)算法。
思路有点像滑动窗口,即使用两个指针分别作为窗口的left、right边界,如果窗口中的sum小于K,则一直往前移动right,当sum >= K时,这时我们又考虑缩小窗口(右移left),这样就会得到一个结果,用这个结果去更新minRes。
不过这里有点特殊的是当A[i] <= 0,这种负值对于子数组的和超过K不起任何作用,因此我们需要将它尽可能从子数组中剔除,特别是当A[i] << 0时,可能造成A[left] + A[left + 1] + … + A[i] <= 0,也就是说A[i]远小于0,造成[left, i]这一段都是子数组中的无用元素。
class Solution {
public:
int shortestSubarray(vector<int>& A, int K) {
int len = A.size(), minRes = INT_MAX;//minRes记录最短子数组
int sum = 0, left = 0;//sum记录当前窗口中的元素和,left左边界
//right为右边界
for (int right = 0; right < len; ++right) {
if (A[right] >= K) {
//如果单个元素超过了K,则最优解必定是1
return 1;
}
sum += A[right];//放入滑动窗口
if (sum < 1) {
//如果窗口sum小于1,则说明窗口中的元素都是在做无用功,因为我们需要找到大于K的最短子数组
sum = 0;//清空窗口
left = right + 1;//移动左边界,方便下一个窗口的形成
continue;
}
//特殊操作,如果当前右边界附近出现一连串的负值,则把他们累加转到他们之前的正值上
//这样处理并不会影响窗口求和,蛋式在缩小左边界时候将会起到巨大的作用,比如A[i] << 0 造成A[left] + A[left + 1] + .. + A[i] <= 0
for (int j = right - 1; A[j + 1] < 0; --j) {
A[j] += A[j + 1];//将负值转移到它前一个元素上
A[j + 1] = 0;//自己清空
}
if (sum >= K) {
//窗口sum不小于K,这时我们尝试右移左边界left,尽量减小窗口大小
while (sum - A[left] >= K || A[left] <= 0) {
sum -= A[left++];
}
minRes = min(minRes, right - left + 1);//right - left + 1表示窗口大小
}
}
return minRes == INT_MAX ? -1 : minRes;
}
};
这个算法最精华的部分就是当前窗口右边界附近出现一连串的负值,把他们累加转到他们之前的正值上
,这样下次这一段出现在某个窗口的左边界附近的时候可以快速剔除。