單調棧以及應用(含leetCode第42題接雨水代碼)

單調棧

定義

單調棧的定義:元素值滿足單調性的棧結構。與單調隊列相比,其只在一端進行進出。遞增棧是棧頂到棧底依次遞增。

插入過程中,保持棧內一直是單調性,將不滿足的元素pop出棧,直接丟棄。如下圖,分別插入6,10,3,7,4,12的時候,單調遞增棧和單調遞減棧的情況分別是樣子的:
在這裏插入圖片描述

應用場景

解決的是以某個值爲最小(最大)值的最大區間,遞增棧能表示入棧元素左邊第一個比它大的元素,遞減棧能表示入棧元素左邊第一個比它小的元素。

我們拿上面圖的單調遞增棧來舉例說明,源數組:[6, 10, 3, 7, 4, 12]
在這裏插入圖片描述

實現代碼:

//獲取左邊第一個小於自己的數,構造一個單調遞減棧
    private int[] getLeftMinNum(int[] src) {
        int[] result = new int[src.length];
        Stack<Integer> monotoneStack = new Stack<>();
        for (int i = 0; i < src.length; i++) {
            if (monotoneStack.isEmpty()) {
                monotoneStack.push(src[i]);
                result[i] = 0;
            } else {
                while (!monotoneStack.isEmpty() && src[i] < monotoneStack.peek()) {
                    monotoneStack.pop();
                }
                if (!monotoneStack.isEmpty()) {
                    result[i] = monotoneStack.peek();
                } else {
                    result[i] = -1;
                }
                monotoneStack.push(src[i]);
            }
        }
        return result;
    }

單調棧應用示例

例題1:BadHairDay

BadHairDay題目鏈接

Some of Farmer John’s N cows (1 ≤ N ≤ 80,000) are having a bad hair day! Since each cow is self-conscious about her messy hairstyle, FJ wants to count the number of other cows that can see the top of other cows’ heads.
Each cow i has a specified height hi (1 ≤ hi ≤ 1,000,000,000) and is standing in a line of cows all facing east (to the right in our diagrams). Therefore, cow i can see the tops of the heads of cows in front of her (namely cows i+1, i+2, and so on), for as long as these cows are strictly shorter than cow i.

每頭牛隻能看見右邊的牛的頭
假如牛的高度分別爲 [10,3,7,4,12,2]
在這裏插入圖片描述
所以總的count = 3+1+1 = 5
思路:找到能看到的最大的牛的index,比如10就只需要找到右邊第一個比自己高的牛的index,然後index-1就是自己能看到的最遠的那頭牛。
所以這個問題就簡化爲找到右邊第一個比自己大的數的index,正好就是單調遞增棧的功能。

實現代碼

 /**
     * 10,3,7,4,12,2
     * 3 ,0,1,0, 1,0
     * 結果爲5
     *
     * @param cows 數組
     * @return 結果
     */
    private int badHair(int[] cows) {
        Stack<Integer> minIndexStack = new Stack<>();
        int result = 0;
        for (int i = cows.length - 1; i >= 0; i--) {
            while (!minIndexStack.isEmpty() && cows[i] > cows[minIndexStack.peek()]) {
                //當前元素大於棧頂元素,棧頂元素比自己小需要彈出頂部元素
                minIndexStack.pop();
            }
            int bigNumIndex;
            if (minIndexStack.isEmpty()) {
                bigNumIndex = cows.length;//如果棧裏沒有數據了,說明自己是最高的,可以看完整個隊伍。
            } else {
                bigNumIndex = minIndexStack.peek();
            }
            minIndexStack.push(i);
            int current = bigNumIndex - i - 1;//-1是因爲看不到最高的那個,所以需要把最高的刨除掉。
            result += current;
        }
        return result;
    }

例題2:接雨水

接雨水題目鏈接
給定 n 個非負整數表示每個寬度爲 1 的柱子的高度圖,計算按此排列的柱子,下雨之後能接多少雨水。

在這裏插入圖片描述

輸入: [0,1,0,2,1,0,1,3,2,1,2,1]
輸出: 6

積水只有在左右大中間小的情況下會行成。正常的思路下我們只要找到兩遍比較大的就可以找到積水,但是如果未來出現更高的臺階我們必須回過頭來看之前的計算是不是有效的。
我們換種思路,一個水潭可以由底層的加上高層的組成,像下圖這樣。
在這裏插入圖片描述

而且這兩個可以不影響,我們可以先得到淺藍色的部分,後面如果出現更高的再加上深藍色的部分。如果不出現就不加。

我們同樣使用單調遞增棧,我們知道棧有兩個操作,入棧和出棧。單調棧的出入棧表示:

入棧,表明本身比棧頂小,說明在下臺階,下臺階是行程不了積水的。
出棧,表明本身比棧頂大,肯定會形成積水。

所以每次計算積水肯定是在pop的時候計算。而且棧裏最少有兩個元素的時候纔會形成,因爲最小的積水也是有兩個邊和一個坑組成的,最少也是棧裏兩個加上剛來的一個。
這裏我們可以想象成一個木桶,根據木桶理論,容量由最低的那塊木板決定,所以桶的容量需要由 長木板+桶底+短木板決定
我們看上面圖的例子:
(1)遍歷到3的時候。棧:[1, 0],來了一個2,2比0大,0要出棧,這個時候就可以知道1和2中間夾了一個0,找1和2最小值,短木板是1,桶底是0,所以寬度是1,高度是1,得到面積是1.
(2)遍歷到6的時候。棧:[2,1,0],來了1,所以1和1中間夾了0,同樣得到面積是1,得到的是淺藍色的部分。
(3)繼續往後遍歷來到7,來了一個3,棧:[2,1] (看入棧的邏輯,棧裏也可以是[2,1,1],相同的1可以入可以不入)。假設是[2,1],先彈1,短木板是1,長木板是3,但是桶底也是1,所以能裝的水是0。1彈出之後棧裏還剩[2],短木板是2,長木板是3,桶底是1,水深度爲1,寬度index=6-3=3,所以面積是3.

代碼實現

//力扣42. 接雨水   https://www.jianshu.com/p/6bbd3653a57f
    //使用遞增棧,當push時,說明高度在下降;當pop時說明高度在上升,當棧中至少有兩個元素,且做pop操作說明中間有水坑。注意水坑是一層一層的進行計算
    public int trap(int[] height) {
        if (height == null || height.length == 0)
            return 0;
        Stack<Integer> incre=new Stack<>();
        int result=0;
        for(int i=0;i<height.length;i++){
        while(!incre.isEmpty()&&height[incre.peek()]<=height[i]){
            if(incre.size()>=2){
               int low=incre.pop();
               int top=incre.peek();
               int level=Math.min(height[top]-height[low],height[i]-height[low]);
               int dis=i-top-1;
               result+=dis*level;
            }
            else{
               incre.pop();
            }
        }
        incre.push(i);
        }
        return result;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章