話說博主在寫 Max Chunks To Make Sorted II 這篇帖子的解法四時,寫到使用單調棧Monotone Stack的解法時,忽然腦中觸電通常,想起了以前曾經在此貼 LeetCode All in One 題目講解彙總(持續更新中...) 的留言區中說要寫單調棧的總結帖,當時答應了要寫,就去 LeetCode 上看標記爲 Stack 的題,但是發現有好多題,並且不少用的不是單調棧,因而博主一個一個的看了起來,可是無奈太多了,一直沒有時間所有看完,就一直沒有動筆寫。雖然說時間就像那啥,擠擠總會有的,可是這不一個恍惚,半年就過去了,若是博主再不開始寫,等回過神來,絕對又是半年。因而,博主決定改變策略,不去看全部題的,而是好壞很少想,直接動筆先寫個大概,留到之後慢慢補充完整吧。html
好,廢話很少說,來講單調棧吧。所謂的單調棧 Monotone Stack,就是棧內元素都是單調遞增或者單調遞減的,有時候須要嚴格的單調遞增或遞減,根據題目的具體狀況來看吧。關於單調棧,這個帖子講的不錯,並且舉了個排隊的例子來類比。那麼,博主也舉個生動的例子來講明吧:好比有一天,某家店在發 free food,不少人在排隊,因而你也趕過去湊熱鬧。可是因爲來晚了,隊伍已經很長了,想着否則就插個隊啥的。但發現排在隊伍最前面的都是一些有紋身的大佬,惹不起,只能讚美道,小豬佩奇身上紋,來世還作社會人。因而往隊伍後面走,發現是一羣小屁孩,直接所有攆走,而後排在了社會大佬們的後面。那麼這就是一個單調遞減的棧,按實力遞減。因爲棧元素是後進先出的,因此上面的例子正確的檢查順序應該是從隊尾往前遍歷,小屁孩都攆走,直到遇到大佬中止,而後排在大佬後面(假設這個隊列已經事先按實力遞減排好了)。git
明白了單調棧的加入元素的過程後,咱們來看看它的性質,以及爲啥要用單調棧。單調棧的一大優點就是線性的時間複雜度,全部的元素只會進棧一次,並且一旦出棧後就不會再進來了。算法
單調遞增棧能夠找到左起第一個比當前數字小的元素。好比數組 [2 1 4 6 5],剛開始2入棧,數字1入棧的時候,發現棧頂元素2比較大,將2移出棧,此時1入棧。那麼2和1都沒左起比自身小的數字。而後數字4入棧的時候,棧頂元素1小於4,因而1就是4左起第一個小的數字。此時棧裏有1和4,而後數字6入棧的時候,棧頂元素4小於6,因而4就是6左起第一個小的數字。此時棧裏有1,4,6,而後數字5入棧的時候,棧頂元素6大於5,將6移除,此時新的棧頂元素4小於5,那麼4就是5左起的第一個小的數字,最終棧內數字爲 1,4,5。數組
單調遞減棧能夠找到左起第一個比當前數字大的元素。這裏就不舉例說明了,一樣的道理,你們能夠自行驗證一下。app
性質搞懂了後,下面來看一下應用,什麼樣的場景下適合使用單調棧呢?能夠看下 Max Chunks To Make Sorted II 這篇帖子的解法四,但這道題並非單調棧的最典型應用,只能說能想到用單調棧確實牛b,但通常狀況下是不容易想到的。咱們來看一些特別適合用單調棧來作的題目吧。post
首推 Trapping Rain Water 這道題,雖然博主開始也沒有注意到可使用單調棧來作。但其實是一道至關合適的題,來複習一下題目:url
For example,
Given [0,1,0,2,1,0,1,3,2,1,2,1]
, return 6
..net
給了邊界的高度(黑色部分),讓求能裝的水量(藍色部分)。 爲啥能用單調棧來作呢?咱們先來考慮一下,什麼狀況下能夠裝下水呢,是否是必須兩邊高,中間低呢?咱們對低窪的地方感興趣,就可使用一個單調遞減棧,將遞減的邊界存進去,一旦發現當前的數字大於棧頂元素了,那麼就有可能會有能裝水的地方產生。此時咱們當前的數字是右邊界,咱們從棧中至少須要有兩個數字,才能造成一個坑槽,先取出的那個最小的數字,就是坑槽的最低點,再次取出的數字就是左邊界,咱們比較左右邊界,取其中較小的值爲裝水的邊界,而後此高度減去水槽最低點的高度,乘以左右邊界間的距離就是裝水量了。因爲須要知道左右邊界的位置,因此咱們雖然維護的是遞減棧,可是棧中數字並非存遞減的高度,而是遞減的高度的座標。這應該屬於單調棧的高級應用了,可能並非那麼直接就能想出正確的解法。code
再來看一道 Largest Rectangle in Histogram,這道求直方圖中的最大矩陣的題,也是很是適合用單調棧來作的,來複習一下題目:htm
For example,
Given height = [2,1,5,6,2,3]
,
return 10
.
咱們能夠看到,直方圖矩形面積要最大的話,須要儘量的使得連續的矩形多,而且最低一塊的高度要高。有點像木桶原理同樣,老是最低的那塊板子決定桶的裝水量。那麼既然須要用單調棧來作,首先要考慮到底用遞增棧,仍是用遞減棧來作。咱們想啊,遞增棧是維護遞增的順序,當遇到小於棧頂元素的數就開始處理,而遞減棧正好相反,維護遞減的順序,當遇到大於棧頂元素的數開始處理。那麼根據這道題的特色,咱們須要按從高板子到低板子的順序處理,先處理最高的板子,寬度爲1,而後再處理旁邊矮一些的板子,此時長度爲2,由於以前的高板子可組成矮板子的矩形 ,所以咱們須要一個遞增棧,當遇到大的數字直接進棧,而當遇到小於棧頂元素的數字時,就要取出棧頂元素進行處理了,那取出的順序就是從高板子到矮板子了,因而乎遇到的較小的數字只是一個觸發,表示如今須要開始計算矩形面積了,爲了使得最後一塊板子也被處理,這裏用了個小trick,在高度數組最後面加上一個0,這樣原先的最後一個板子也能夠被處理了。因爲棧頂元素是矩形的高度,那麼關鍵就是求出來寬度,那麼跟以前那道 Trapping Rain Water 同樣,單調棧中不能放高度,而是須要放座標。因爲咱們先取出棧中最高的板子,那麼就能夠先算出長度爲1的矩形面積了,而後再取下一個板子,此時根據矮板子的高度算長度爲2的矩形面積,以此類推,直到數字大於棧頂元素爲止,再次進棧,巧妙的一比!
初步來總結一下單調棧吧,單調棧實際上是一個看似原理簡單,可是能夠變得很難的解法。線性的時間複雜度是其最大的優點,每一個數字只進棧並處理一次,而解決問題的核心就在處理這塊,當前數字若是破壞了單調性,就會觸發處理棧頂元素的操做,而觸發數字有時候是解決問題的一部分,好比在 Trapping Rain Water 中做爲右邊界。有時候僅僅觸發做用,好比在 Largest Rectangle in Histogram 中是爲了開始處理棧頂元素,若是僅做爲觸發,可能還須要在數組末尾增長了一個專門用於觸發的數字。另外須要注意的是,雖然是遞增或遞減棧,但裏面實際存的數字並不必定是遞增或遞減的,由於咱們能夠存座標,而這些座標帶入數組中才會獲得遞增或遞減的數。因此對於玩數組的題,若是相互之間關聯很大,那麼就能夠考慮考慮單調棧可否解題。
應用實例:
Largest Rectangle in Histogram
相關帖子:
LeetCode Binary Search Summary 二分搜索法小結
參考資料:
https://zhuanlan.zhihu.com/p/26465701
https://chuckliu.me/#!/posts/585a2cb4f33c18149026f0be
https://blog.csdn.net/liujian20150808/article/details/50752861