概念:
單調棧(monotone-stack)是指棧內元素(棧底到棧頂)都是(嚴格)單調遞增或者單調遞減的。
如果有新的元素入棧,棧調整過程中 *會將所有破壞單調性的棧頂元素出棧,並且出棧的元素不會再次入棧* 。由於每個元素只有一次入棧和出棧的操作,所以 *單調棧的維護時間複雜度是O(n)* 。
單調棧性質:
1. 單調棧裏的元素具有單調性。
2. 遞增(減)棧中可以找到元素左右兩側比自身小(大)的第一個元素。
我們主要使用第二條性質,該性質主要體現在棧調整過程中,下面以遞增棧爲例(假設所有元素都是唯一),當新元素入棧。
+ 對於出棧元素來說:找到右側第一個比自身小的元素。
+ 對於新元素來說:等待所有破壞遞增順序的元素出棧後,找到左側第一個比自身小的元素。
leetcode 84實例:
https://leetcode-cn.com/problems/largest-rectangle-in-histogram/
給定 n 個非負整數,用來表示柱狀圖中各個柱子的高度。每個柱子彼此相鄰,且寬度爲 1 。
求在該柱狀圖中,能夠勾勒出來的矩形的最大面積。
以上是柱狀圖的示例,其中每個柱子的寬度爲 1,給定的高度爲 [2,1,5,6,2,3]。
圖中陰影部分爲所能勾勒出的最大矩形面積,其面積爲 10 個單位。
示例:
輸入: [2,1,5,6,2,3]
輸出: 10
用單調棧解題:
- 圖解詳細步驟:
這裏很巧妙的一點是:每次有元素出棧時計算面積(並與maxArea比較,更新最大值),pop出棧的元素是所求矩形的height對應的index(由此可獲取到矩形的高),pop之後的棧頂(peek)元素是該矩形的寬的左邊界,當前for循環中的i所表示的index是寬的右邊界,“右邊界-左邊界-1”即爲該矩形的寬。
此圖結合下圖step4來看,index=2出棧的時候,計算該面積,此時height[2]=5-->高,棧中剩下[-1,1],(因爲3在2之前已經出棧,4還未入棧),棧頂元素爲1-->左邊界,當前循環索引i=4--->右邊界,則面積=(右邊界-左邊界-1)*高=(4-1-1)*5=10.
這裏把棧水平放置,方便對照原圖橫座標來看。
單調棧代碼(Java):
// 單調棧!!!
public int largestRectangleArea2(int[] heights) {
int len = heights.length;
if(len==0) return 0;
if(len==1) return heights[0];
int maxArea = 0;
int[] newHeights = new int[len+2];
System.arraycopy(heights,0,newHeights,1,len);
Deque<Integer> stack = new ArrayDeque<>();
len += 2;
for (int i = 0; i < len; i++) {
while(!stack.isEmpty() && newHeights[i]<newHeights[stack.peek()]){
int curHei = newHeights[stack.poll()];
maxArea = Math.max(maxArea,(i-stack.peek()-1)*curHei);
}
stack.push(i);
}
return maxArea;
}
附(暴力解法代碼)(Java):
// 暴力
public int largestRectangleArea(int[] heights) {
int len = heights.length;
if(len<1) return 0;
int maxArea = 0;
for (int i = 0; i < len; i++) {
int minHi = heights[i];
// maxArea = Math.max(maxArea,heights[i]);
for (int j = i+1; j < len; j++) {
minHi = Math.min(minHi,heights[j]);
maxArea = Math.max(maxArea,minHi*(j+1-i));
}
}
return maxArea;
}
小結:
單調棧的作用/什麼時候用單調棧:
單調棧可以以 O(1) 的時間複雜度得知某個位置左右兩側比他大(或小)的數的位置,當你需要高效率獲取某個位置左右兩側比他大(或小)的數的位置的的時候就可以用到單調棧。
說人話,結合上題,就是當從左到右遍歷的過程中(找右邊界),又需要回頭查找(即從右到左找左邊界),這就像極了棧結構的LIFO(後進先出/倒序輸出),此時就需要利用單調棧。
我們在緩存數據的時候,是從左向右緩存的,我們計算出一個結果的順序是從右向左的,並且計算完成以後我們就不再需要了,符合後進先出的特點。因此,我們需要的這個作爲緩存的數據結構就是棧。
當確定了一個柱形的高度的時候,我們就將它從棧頂移出,所有的柱形在棧中進棧一次,出棧一次,一開始棧爲空,最後也一定要讓棧爲空,表示這個高度數組裏所有的元素都考慮完了。
求解數組中元素右邊第一個比它小的元素的下標,從前往後,構造單調遞增棧;
求解數組中元素右邊第一個比它大的元素的下標,從前往後,構造單調遞減棧;
求解數組中元素左邊第一個比它大的元素的下標,從後往前,構造單調遞減棧;
求解數組中元素左邊第一個比它小的元素的下標,從後往前,構造單調遞增棧。
以下列出了單調棧的問題,供大家參考。
序號 題目 題解
1 42. 接雨水(困難) 暴力解法、優化、雙指針、單調棧
2 739. 每日溫度(中等) 暴力解法 + 單調棧
3 496. 下一個更大元素 I(簡單) 暴力解法、單調棧
4 316. 去除重複字母(困難) 棧 + 哨兵技巧(Java、C++、Python)
5 901. 股票價格跨度(中等) 「力扣」第 901 題:股票價格跨度(單調棧)
6 402. 移掉K位數字
7 581. 最短無序連續子數組
另有詳細圖解說明請參考leetcode大神:
https://leetcode-cn.com/problems/largest-rectangle-in-histogram/solution/84-by-ikaruga/
如有錯誤或不當之處,請批評指正!