LeetCode #862 Shortest Subarray with Sum at Least K

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SquirrelYuyu/article/details/82962952


(Week 5 算法博客)

题目

Shortest Subarray with Sum at Least K

Return the length of the shortest, non-empty, contiguous subarray of A with sum at least K.

If there is no non-empty subarray with sum at least K, return -1.

Example 1:

Input: A = [1], K = 1
Output: 1

Example 2:

Input: A = [1,2], K = 4
Output: -1

Example 3:

Input: A = [2,-1,2], K = 3
Output: 3

Note:

  1. 1 <= A.length <= 50000
  2. -10 ^ 5 <= A[i] <= 10 ^ 5
  3. 1 <= K <= 10 ^ 9

Difficulty: Hard

分析过程

这个问题要求出所给数组中,所有数之和至少为K的子数组的最短长度。

暴力求解

建立 long int 类型的数组 sumssums[i] 表示前 i 个数的和。那么,就可以由 sums[j] - sums[i] 求出从第 i + 1 个数到第 j 个数的和了。这样只要遍历一次源数组,比对每个数对 ( i , j ) 都相加这个区间内的若干个数要高效。

然后,对每个数对 ( i , j ) ,都检查 sums[j] - sums[i] >= K 是否成立。时间复杂度是 O ( N 2 ) O(N^2)

class Solution {
public:
    int shortestSubarray(vector<int>& A, int K) {
    	const int size2 = A.size() + 1;
    	long int sums[size2] = {0};
    	int min = A.size();
        int sum = 0;
        for(int i = 1; i < size2; i++){
        	if(A[i - 1] >= K) return 1;
        	sums[i] = sums[i - 1] + A[i - 1];
		}
		
		for(int i = 0; i < size2; i++){
			for(int j = i + 1; j < size2 && j < i + min; j++){
				if(sums[j] - sums[i] >= K && min > j - i){
					min = j - i;
					break;
				}
			}
		}
		
		if(min == A.size() && sums[A.size()] < K) return -1;
		return min;
    }
    
};

但是,提交后结果是 Time Limit Exceeded 。有几个源数组很长的输入,这个算法还不够高效。

寻找优化

来看看这个问题中,有哪些能用于优化算法的性质。

可以把 sums 想象成一个折线图,横坐标是 sums 数组的索引,纵坐标是索引对应的元素值。两个相邻的点 sums[i]sums[i + 1] 之间的线段,可看作 A[i]

一个符合要求的子数组可由 (begin , end) 来定义,即这个数组之和等于 sums[end] - sums[begin] ,它包括了第 begin + 1 个数到第 end 个数。

sums[begin] 右边应该是一条上升的线段,也即 A[begin] > 0sums[begin] < sums[begin + 1]。因为如果 sums[begin] >= sums[begin + 1] ,那么 end - (begin + 1) 是比 end - begin 更小、更好的答案。

sums[end] 左边也应该是一条上升的线段,也即 A[end - 1] > 0sums[end - 1] < sums[end]。因为如果 sums[end - 1] >= sums[end] ,那么 (end - 1) - begin 同样是比 end - begin 更小、更好的答案。

综上,我们只要考虑一条上升线段的两个端点就行了。所以,如下面代码,建立 increase ,来记录那些满足 A[i] > 0i 们。

③ 如果选定 begin 后开始寻找对应的 end ,在途中遇到了 sums[i] < sums[begin] 呢?那么对满足这个 beginend 来说,i 是比 begin 更优的 begin

所以,如下面代码,在寻找 end (即 y )的同时可以更新 minIndex ,也即在 end 之前,最小的 sums[i] 。下一个 begin 应为 minIndex 的下一个数。

class Solution {
public:
    int shortestSubarray(vector<int>& A, int K) {
    	const int size2 = A.size() + 1;
    	long int sums[size2] = {0};
    	int min = size2;
        int sum = 0;
        for(int i = 1; i < size2; i++){
        	if(A[i - 1] >= K) return 1;
        	sums[i] = sums[i - 1] + A[i - 1];
		}
		
		vector<int> increase;
		for(int i = 0; i < A.size(); i++){
        	if(A[i] > 0) increase.push_back(i);
		}
		
		for(int i = 0; i < increase.size(); ){
			int minIndex = increase[i];
			for(int j = i + 1; j < increase.size(); j++){
				int y = increase[j];
				if(sums[y] <= sums[minIndex]){
					minIndex = y;
					i = j;
				}
				else if(sums[y + 1] >= sums[minIndex] + K && min > y + 1 - minIndex){
					min = y + 1 - minIndex;
					break;
				}
			}
			i++;
		}
		
		if(min == size2) return -1;
		return min;
    }
    
};

时间复杂度仍是 O ( N 2 ) O(N^2) 。而结果,还是 Time Limit Exceeded

怎么办呢?

由end寻找begin

上面的算法是由 begin 寻找 end 。如果由 end 寻找 begin 呢?

④ 由 ② 可知,对某个 end 来说,如果存在点 sums[i] >= sums[end]i < end ,那么这个 i 不会是这个 end 及后来的 end 们的最佳 begin 。所以,我们可以把这个点 i 去掉,不计算 sums[end] - sums[i]

在这里插入图片描述

⑤ 第 ④ 个性质中,去掉 i 的操作可以产生一个上升的点序列。对这个 end 来说,如果它的最佳 begin 找到了,那么前面的其他小于 sums[begin]sums[i] 也再不用被这个 end 及后面的其他 end 们考虑。

class Solution {
public:
    int shortestSubarray(vector<int>& A, int K) {
    	const int size2 = A.size() + 1;
    	long int sums[size2] = {0};
    	int min = size2;
        int sum = 0;
        for(int i = 1; i < size2; i++){
        	if(A[i - 1] >= K) return 1;
        	sums[i] = sums[i - 1] + A[i - 1];
		}
		
		deque<int> list;
		for(int end = 0; end < size2; end++){
			while(!list.empty() && sums[list.back()] >= sums[end]){
				list.pop_back();
			}
			while(!list.empty() && sums[list.front()] + K <= sums[end]){
				int distance = end - list.front();
				list.pop_front();
				if(min > distance) min = distance;
			}
			list.push_back(end);
		}
		
		if(min == size2) return -1;
		return min;
    }
};

时间复杂度是 O ( N ) O(N) 。结果:AC!

结语

在【分析过程】中写的【寻找优化】,和它之后的【由end寻找begin】相比,看起来前者好像没什么用。但是记录下自己曲折的思考过程,我想这也是一种总结、一种收获吧。

还有就是,这道题真难啊,最终还是看了 LeetCode 上的 Solution 才 AC,我好菜啊。

猜你喜欢

转载自blog.csdn.net/SquirrelYuyu/article/details/82962952
今日推荐