單調(diào)棧和應(yīng)用實(shí)踐

什么是單調(diào)棧

單調(diào)棧的定義:單調(diào)棧即滿足單調(diào)性的棧結(jié)構(gòu)走敌。與單調(diào)隊(duì)列相比碴倾,其只在一端進(jìn)行進(jìn)出。

如何使用單調(diào)棧

單調(diào)棧分為單調(diào)遞增棧單調(diào)遞減棧掉丽,顧名思義跌榔,就是棧內(nèi)元素是升序還是降序排列的,也涉及到出棧的邏輯捶障。
如下圖僧须,分別插入6,10,3,7,4,12的時(shí)候,單調(diào)遞增棧和單調(diào)遞減棧的情況分別是樣子的:

單調(diào)棧有什么應(yīng)用项炼?

\color{red}{單調(diào)遞增棧}能表示入棧元素左邊第一個(gè)比它\color{red}{大}的元素
\color{red}{單調(diào)遞減棧}能表示入棧元素左邊第一個(gè)比它\color{red}{小}的元素

我們拿上面圖的單調(diào)遞增棧來舉例說明:
源數(shù)組:[6, 10, 3, 7, 4, 12]

處理元素 單調(diào)棧內(nèi) 找到元素 補(bǔ)充說明
6 [6] 棧為空担平,說明左邊沒有比它大的了
10 [10] 棧頂元素6比自己(10)小,為了維持單調(diào)遞減锭部,6出棧暂论,10入棧
3 [10, 3] 10 3比10小,直接入棧
7 [10, 7] 10 7比3大拌禾,為了不保證遞減取胎,3出棧,7入棧
4 [10, 7, 4] 7 4比7小湃窍,直接入棧
12 [12] 12比棧里所有元素都大闻蛀,彈完后棧空坝咐,找不到比自己大的循榆。

代碼實(shí)現(xiàn):

//獲取左邊第一個(gè)小于自己的數(shù),構(gòu)造一個(gè)單調(diào)遞減棧
    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;
    }

按照這個(gè)原理墨坚,大家可以自己推一下遞增棧查找第一個(gè)小元素秧饮。

例題

例題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]

index 自己的高度 能看到哪幾頭 數(shù)量
0 10 3,7,4 3
1 3 null 0
2 7 4 1
3 4 null 0
4 12 2 1
5 2 null 0

所以總的count = 3+1+1 = 5
思路:找到能看到的最大的牛的index泼各,比如10就只需要找到右邊第一個(gè)比自己高的牛的index,然后index-1就是自己能看到的最遠(yuǎn)的那頭牛亏拉。
所以這個(gè)問題就簡化為找到右邊第一個(gè)比自己大的數(shù)的index扣蜻,正好就是單調(diào)遞增棧的功能逆巍。

 /**
     * 10,3莽使,7锐极,4,12芳肌,2
     * 3 灵再,0,1亿笤,0翎迁, 1,0
     * 結(jié)果為5
     *
     * @param cows 數(shù)組
     * @return 結(jié)果
     */
    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()]) {
                //當(dāng)前元素大于棧頂元素净薛,棧頂元素比自己小需要彈出頂部元素
                minIndexStack.pop();
            }
            int bigNumIndex;
            if (minIndexStack.isEmpty()) {
                bigNumIndex = cows.length;//如果棧里沒有數(shù)據(jù)了汪榔,說明自己是最高的,可以看完整個(gè)隊(duì)伍肃拜。
            } else {
                bigNumIndex = minIndexStack.peek();
            }
            minIndexStack.push(i);
            int current = bigNumIndex - i - 1;//-1是因?yàn)榭床坏阶罡叩哪莻€(gè)揍异,所以需要把最高的刨除掉。
            result += current;
        }
        return result;
    }
例題2:接雨水

https://leetcode-cn.com/problems/trapping-rain-water/
給定 n 個(gè)非負(fù)整數(shù)表示每個(gè)寬度為 1 的柱子的高度圖爆班,計(jì)算按此排列的柱子,下雨之后能接多少雨水辱姨。

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

此思路借鑒自https://blog.csdn.net/qq_37236745/article/details/83795858
我們分析一下這個(gè)題目:
積水只有在左右大中間小的情況下會(huì)行成柿菩。正常的思路下我們只要找到兩遍比較大的就可以找到積水,但是如果未來出現(xiàn)更高的臺(tái)階我們必須回過頭來看之前的計(jì)算是不是有效的雨涛。
我們換種思路枢舶,一個(gè)水潭可以由底層的加上高層的組成,像下圖這樣替久。


而且這兩個(gè)可以不影響凉泄,我們可以先得到淺藍(lán)色的部分,后面如果出現(xiàn)更高的再加上深藍(lán)色的部分蚯根。如果不出現(xiàn)就不加后众。

我們同樣使用單調(diào)遞增棧,我們知道棧有兩個(gè)操作颅拦,入棧和出棧蒂誉。單調(diào)棧的出入棧表示:

入棧,表明本身比棧頂小距帅,說明在下臺(tái)階右锨,下臺(tái)階是行程不了積水的。
出棧碌秸,表明本身比棧頂大绍移,肯定會(huì)形成積水悄窃。

所以每次計(jì)算積水肯定是在pop的時(shí)候計(jì)算。而且棧里最少有兩個(gè)元素的時(shí)候才會(huì)形成蹂窖,因?yàn)樽钚〉姆e水也是有兩個(gè)邊和一個(gè)坑組成的轧抗,最少也是棧里兩個(gè)加上剛來的一個(gè)。
這里我們可以想象成一個(gè)木桶恼策,根據(jù)木桶理論鸦致,容量由最低的那塊木板決定,所以桶的容量需要由 長木板+桶底+短木板決定
我們看上面圖的例子:

  • 遍歷到3的時(shí)候涣楷。棧:[1, 0]分唾,來了一個(gè)2,2比0大狮斗,0要出棧绽乔,這個(gè)時(shí)候就可以知道1和2中間夾了一個(gè)0,找1和2最小值碳褒,短木板是1折砸,桶底是0,所以寬度是1沙峻,高度是1睦授,得到面積是1.
  • 遍歷到6的時(shí)候。棧:[2摔寨,1去枷,0],來了1是复,所以1和1中間夾了0删顶,同樣得到面積是1,得到的是淺藍(lán)色的部分淑廊。
  • 繼續(xù)往后遍歷來到7逗余,來了一個(gè)3,棧:[2,1] (看入棧的邏輯季惩,棧里也可以是[2,1,1]录粱,相同的1可以入可以不入)。假設(shè)是[2,1]画拾,先彈1关摇,短木板是1,長木板是3碾阁,但是桶底也是1输虱,所以能裝的水是0。1彈出之后棧里還剩[2]脂凶,短木板是2宪睹,長木板是3愁茁,桶底是1,水深度為1亭病,寬度index=6-3=3鹅很,所以面積是3.

代碼實(shí)現(xiàn):

private int trap(int[] height) {
        if (height == null || height.length == 0)
            return 0;
        int sumArea = 0;
        Stack<Integer> stack = new Stack<>();
        for (int right = 0; right < height.length; right++) {
            while (!stack.isEmpty() && height[stack.peek()] <= height[right]) {
                if (stack.size() >= 2) {
                    int j = stack.pop();
                    int left = stack.peek();
                    int waterHeight = Math.min(height[right], height[left]) - height[j];//就像一個(gè)木桶:得到最低的木板減去底得到能裝水的高度
                    int waterLength = right - left - 1;
                    int curArea = waterHeight * waterLength;
                    sumArea += curArea;
                } else {
                    stack.pop();
                }
            }
            stack.push(right);
        }
        return sumArea;
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市罪帖,隨后出現(xiàn)的幾起案子促煮,更是在濱河造成了極大的恐慌,老刑警劉巖整袁,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件菠齿,死亡現(xiàn)場離奇詭異,居然都是意外死亡坐昙,警方通過查閱死者的電腦和手機(jī)绳匀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來炸客,“玉大人疾棵,你說我怎么就攤上這事”韵桑” “怎么了是尔?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長开仰。 經(jīng)常有香客問我嗜历,道長,這世上最難降的妖魔是什么抖所? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮痕囱,結(jié)果婚禮上田轧,老公的妹妹穿的比我還像新娘。我一直安慰自己鞍恢,他們只是感情好傻粘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著帮掉,像睡著了一般弦悉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蟆炊,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天稽莉,我揣著相機(jī)與錄音,去河邊找鬼涩搓。 笑死污秆,一個(gè)胖子當(dāng)著我的面吹牛劈猪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播良拼,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼战得,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了庸推?” 一聲冷哼從身側(cè)響起常侦,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎贬媒,沒想到半個(gè)月后聋亡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掖蛤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年杀捻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蚓庭。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡致讥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出器赞,到底是詐尸還是另有隱情垢袱,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布港柜,位于F島的核電站请契,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏夏醉。R本人自食惡果不足惜爽锥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望畔柔。 院中可真熱鬧氯夷,春花似錦、人聲如沸靶擦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春聂薪,著一層夾襖步出監(jiān)牢的瞬間倦春,已是汗流浹背涩拙。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工蚊丐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓捞蛋,卻偏偏與公主長得像孝冒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子拟杉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容