1. 程式人生 > >LeetCode #862 Shortest Subarray with Sum at Least K

LeetCode #862 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(N2)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(N2)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,我好菜啊。