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 <= A.length <= 50000
-10 ^ 5 <= A[i] <= 10 ^ 5
1 <= K <= 10 ^ 9
Difficulty: Hard
分析過程
這個問題要求出所給陣列中,所有數之和至少為K的子陣列的最短長度。
暴力求解
建立 long int
型別的陣列 sums
,sums[i]
表示前 i 個數的和。那麼,就可以由 sums[j] - sums[i]
求出從第 i + 1 個數到第 j 個數的和了。這樣只要遍歷一次源陣列,比對每個數對 ( i , j ) 都相加這個區間內的若干個數要高效。
然後,對每個數對 ( i , j ) ,都檢查 sums[j] - sums[i] >= K
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] > 0
或 sums[begin] < sums[begin + 1]
。因為如果 sums[begin] >= sums[begin + 1]
,那麼 end - (begin + 1)
是比 end - begin
更小、更好的答案。
② sums[end]
左邊也應該是一條上升的線段,也即 A[end - 1] > 0
或 sums[end - 1] < sums[end]
。因為如果 sums[end - 1] >= sums[end]
,那麼 (end - 1) - begin
同樣是比 end - begin
更小、更好的答案。
綜上,我們只要考慮一條上升線段的兩個端點就行了。所以,如下面程式碼,建立 increase
,來記錄那些滿足 A[i] > 0
的 i
們。
③ 如果選定 begin
後開始尋找對應的 end
,在途中遇到了 sums[i] < sums[begin]
呢?那麼對滿足這個 begin
的 end
來說,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;
}
};
時間複雜度仍是 。而結果,還是 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;
}
};
時間複雜度是 。結果:AC!
結語
在【分析過程】中寫的【尋找優化】,和它之後的【由end尋找begin】相比,看起來前者好像沒什麼用。但是記錄下自己曲折的思考過程,我想這也是一種總結、一種收穫吧。
還有就是,這道題真難啊,最終還是看了 LeetCode 上的 Solution 才 AC,我好菜啊。