什麼是單調棧?
- 單調棧實際上就是棧,只是限制要比普通的棧更嚴格而已了。要求是每次入棧的元素必須要有序(如果新元素入棧不符合要求,則將之前的元素出棧,直到符合要求再入棧),使之形成單調遞增/單調遞減的一個棧。
比如我們有一個陣列:
-
單調遞增棧:只有比他小的才直接入棧,如果大於就先出棧再入棧(在出棧的時候可以進行一些操作計算,如求溫度那一題就是在出棧時候計算第一個大於該值他們之間的距離)。適用於求解第一個大於該位置元素的數
-
單調遞減棧:只有比他大的才入棧,如果小於的話,也是先出棧再入棧。適用於求第一個小於該位置元素的數
-
如何判斷是 單調遞增/遞減棧 是按照出棧的順序決定的,比如棧為 {1, 2, 6},出棧後,為{6, 2, 1},這是遞減順序的,所以就是單調遞減棧了
單調棧適合的題目是求解 第一個大於 xxx 或者 第一個小於 xxx 這種題目。當出現類似題目時候應該優先想到使用單調棧來解題。
我們來舉一個單調遞減棧的例子:
- 比如我們要將陣列 {1, 3, 4, 5, 2, 9, 6} 壓入棧:
- 首先1入棧,此時棧為:{1}
- 接下來是3,由於 3 > 1 ,直接入棧,此時棧為:{1, 3}
- 接下來是4,由於 4 > 3 ,直接入棧,此時棧為:{1, 3, 4}
- 接下來是5,由於 4 > 3 ,直接入棧,此時棧為:{1, 3, 4, 5}
- 接下來是2,因為 2 < 5,不滿足條件,所以我們將5出棧,此時棧為:{1, 3, 4}
- 然後再將2與4比較,因為 2 < 4 ,還是不滿足條件,所以再將4出棧,此時棧為:{1, 3}
- 然後再將2與3比較,因為 2 < 3 ,還是不滿足條件,所以再將3出棧,此時棧為:{1}
- 然後將2與1比較,因為 2 > 1 ,滿足條件,將2入棧,此時棧為:{1, 2}
- 接下來是9,由於 9 > 2 ,直接入棧,此時棧為:{1, 2, 9}
- 接下來是6,因為 6 < 9 ,不滿足條件,將9出棧,此時棧為:{1, 2}
- 然後6與2比較,6 > 2 ,滿足條件,6入棧,此時棧為{1, 2, 6}
- 最後,目標陣列都遍歷完了,我們就得到了該結果
現在有這樣一個題目:
- 給你一個陣列,返回一個等長的陣列,對應索引儲存著下一個更大的元素,如果沒有更大的元素,就存-1。這個要怎麼解呢?
- 如果輸入的是{2, 1, 2, 4, 3},那麼我們會得到結果{4, 2, 4, -1, -1}
- 這個結果是怎麼得來的捏?向右掃描,比2大的第一個數是4,比1大的第一個數是2,沒有比4大的數(4 > 3),也沒有比3大的數(3 == 3),所以這兩個為-1
可以嘗試一下做下這一題:739. 每日溫度
經過多次做題總結,不難發現,其實是有套路模板的,以後做題只需要修改一些即可:
class Solution {
public int[] monotonousStack(int[] T) {
LinkedList<Integer> stack = new LinkedList<>();
// 用來存放第一個大於該位置元素的距離
int[] res = new int[T.length];
for (int i = 0; i < T.length; i++) {
while (!stack.isEmpty() && T[i] > T[stack.peek()]) {
int peek = stack.pop();
// i - peek 是求解在peek的右邊中,第一個大於T[peek]的元素
res[peek] = i - peek;
}
stack.push(i);
}
return res;
}
}
「複雜度分析」
- 時間複雜度:由於陣列的元素最多隻會入棧,出棧一次,因此時間複雜度仍然是\(O(N)\) ,其中 N 為陣列長度
- 空間複雜度:由於使用了棧, 並且棧的長度最大是和陣列長度一致,因此空間複雜度是 \(O(N)\),其中 N 為陣列長度
對於有些時候,如果會用到陣列的全部元素,即棧中的元素最後都要出棧,那麼很可能因為沒有考慮邊界而無法通過。所以我們可以使用 哨兵法 ,在 {1, 3, 4, 5, 2, 9, 6} 末尾新增一個 -1 作為哨兵,變成了 {1, 3, 4, 5, 2, 9, 6, -1} ,這種技巧可以簡化程式碼邏輯。