單調棧-圖解-LeetCode84柱狀圖中最大的矩形

概念:

單調棧(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/bao-li-jie-fa-zhan-by-liweiwei1419/

https://leetcode-cn.com/problems/largest-rectangle-in-histogram/solution/84-by-ikaruga/

 

 

如有錯誤或不當之處,請批評指正!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章