口述演算法 5:和至少為K的最短連續子陣列長度
問題
對於連續子陣列問題,有幾種常見的思路,比如字首和、動態規劃、單調棧、滑動視窗等。
我們來看這樣一個問題:
給定一個數組 A 和一個整數 K,對於它所有的連續子陣列,篩選出和大於 K 的連續子陣列,返回滿足條件的連續子陣列的最小長度。如果不存在,返回 -1.
分析
連續子陣列的和一般可以用字首和求得,這道題一定是要比較完所有的連續子陣列和大小,如果有某個連續子陣列沒考慮到,那麼肯定無法得到正確答案。
對於陣列的題,遍歷肯定是避免不了的。這道題遍歷原始陣列顯然沒什麼意義,那就從遍歷字首和陣列開始吧。
優化 1
現在我們從字首和陣列的第一個元素開始往後遍歷了,每遍歷一個元素把該元素的下標儲存在雙端佇列裡,同時計算以當前下標為結束符、以第一個元素為起始點的連續子陣列是否滿足條件,一旦某個元素髮現滿足條件,很容易就有如下猜想:有沒有更短的連續子陣列滿足條件呢?於是,從雙端佇列的隊頭開始丟棄元素,直到不能再丟為止。為什麼可以把雙端佇列的那些頭部元素丟棄呢?現在分兩種情況:假如這些元素作為更短的滿足條件的連續子陣列起始點,那麼,這些連續子陣列結束下標肯定就是當前的遍歷位置;假如這些元素作為更短的滿足條件的連續子陣列的結束節點,顯然是更不可能的。
優化 2
上面的操作已經給我們剔除了許多元素,在繼續往後的遍歷中不用在考慮以這些丟棄的元素為起始點的連續子陣列了,那還有沒有方法剔除更多的無用的元素呢?答案是有。
在遍歷字首陣列的過程中,如果發現當前的元素比之前的元素小,那意味著什麼?可以將雙端佇列裡隊尾的元素刪除掉,因為如果存在以這些元素為起始點的連續子陣列,那麼以當前元素為起始點的陣列也能滿足要求且陣列長度更小。
至此,所有的優化都完成了,具體實現程式碼如下:
class Solution {
public:
int shortestSubarray(vector& A, int K) {
int len=A.size();
vector preSum(len + 1, 0);
for (int i=1; i <=len; i++) {
preSum[i]=preSum[i - 1] + A[i - 1];
}
deque dq;
dq.push_back(0);
int ans=INT_MAX;
int j=1;
while (j <=len) {
while (dq.empty()==false && preSum[j] < preSum[dq.back()]) {
dq.pop_back();
}
while (dq.empty()==false && preSum[j] - preSum[dq.front()] >=K)
{
ans=min(j - dq.front(), ans);
dq.pop_front();
}
dq.push_back(j);
j++;
}
if (ans==INT_MAX) {
return -1;
} else {
return ans;
}
}
};
總結
這道題非常典型,幾乎涵蓋了陣列問題的中所有知識點,如字首和、單調棧、雙端佇列等,非常值得我們好好品味。