顧名思義, 單調棧是一種棧。所以要學單調棧,首先要完全搞懂棧。git
棧是一種受限的數據結構, 體如今只容許新的內容從一個方向插入或刪除,這個方向咱們叫棧頂,而從其餘位置獲取內容是不被容許的github
棧最顯著的特徵就是 LIFO(Last In, First Out - 後進先出)算法
舉個例子:編程
棧就像是一個放書本的抽屜,進棧的操做就比如是想抽屜裏放一本書,新進去的書永遠在最上層,而退棧則至關於從裏往外拿書本,永遠是從最上層開始拿,因此拿出來的永遠是最後進去的哪個數組
因爲棧只在尾部操做就好了,咱們用數組進行模擬的話,能夠很容易達到 O(1)的時間複雜度。固然也能夠用鏈表實現,即鏈式棧。瀏覽器
單調棧是一種特殊的棧。棧原本就是一種受限的數據結構了,單調棧在此基礎上又受限了一次(受限++)。數據結構
單調棧要求棧中的元素是單調遞增或者單調遞減的。app
是否嚴格遞增或遞減能夠根據實際狀況來。
這裏我用 [a,b,c] 表示一個棧。 其中 左側爲棧底,右側爲棧頂。編程語言
好比:函數
那這個限制有什麼用呢?這個限制(特性)可以解決什麼用的問題呢?
單調棧適合的題目是求解第一個一個大於 xxx或者第一個小於 xxx這種題目。全部當你有這種需求的時候,就應該想到單調棧。
那麼爲何單調棧適合求解第一個一個大於 xxx或者第一個小於 xxx這種題目?緣由很簡單,我這裏經過一個例子給你們講解一下。
這裏舉的例子是單調遞增棧
好比咱們須要依次將數組 [1,3,4,5,2,9,6] 壓入單調棧。
注意這裏的棧仍然是非空的,若是有的題目須要用到全部數組的信息,那麼頗有可能因沒有考慮邊界而不能經過全部的測試用例。 這裏介紹一個技巧 - 哨兵法,這個技巧常常用在單調棧的算法中。
對於上面的例子,我能夠在原數組 [1,3,4,5,2,9,6] 的右側添加一個小於數組中最小值的項便可,好比 -1。此時的數組是 [1,3,4,5,2,9,6,-1]。這種技巧能夠簡化代碼邏輯,你們儘可能掌握。
上面的例子若是你明白了,就不難理解爲啥單調棧適合求解第一個一個大於 xxx或者第一個小於 xxx這種題目了。好比上面的例子,咱們就能夠很容易地求出在其以後第一個小於其自己的位置。好比 3 的索引是 1,小於 3 的第一個索引是 4,2 的索引 4,小於 2 的第一個索引是 0,可是其在 2 的索引 4 以後,所以不符合條件,也就是不存在在 2 以後第一個小於 2 自己的位置。
上面的例子,咱們在第 6 步開始 pop,第一個被 pop 出來的是 5,所以 5 以後的第一個小於 5 的索引就是 4。同理被 pop 出來的 3,4,5 也都是 4。
若是用 ans 來表示在其以後第一個小於其自己的位置,ans[i] 表示 arr[i] 以後第一個小於 arr[i] 的位置, ans[i] 爲 -1 表示這樣的位置不存在,好比前文提到的 2。那麼此時的 ans 是 [-1,4,4,4,-1,-1,-1]。
第 8 步,咱們又開始 pop 了。此時 pop 出來的是 9,所以 9 以後第一個小於 9 的索引就是 6。
這個算法的過程用一句話總結就是,若是壓棧以後仍然能夠保持單調性,那麼直接壓。不然先彈出棧的元素,直到壓入以後能夠保持單調性。
這個算法的原理用一句話總結就是,被彈出的元素都是大於當前元素的,而且因爲棧是單調增的,所以在其以後小於其自己的最近的就是當前元素了
下面給你們推薦幾道題,你們趁着知識還在腦子來,趕忙去刷一下吧~
上面的算法能夠用以下的僞代碼表示,同時這是一個通用的算法模板,你們遇到單調棧的題目能夠直接套。
建議你們用本身熟悉的編程語言實現一遍,之後改改符號基本就能用。
class Solution: def monostoneStack(self, arr: List[int]) -> List[int]: stack = [] ans = 定義一個長度和 arr 同樣長的數組,並初始化爲 -1 循環 i in arr: while stack and arr[i] > arr[棧頂元素]: peek = 彈出棧頂元素 ans[peek] = i - peek stack.append(i) return ans
複雜度分析
這裏提升兩種編程語言的單調棧模板供你們參考。
Python3:
class Solution: def monostoneStack(self, T: List[int]) -> List[int]: stack = [] ans = [0] * len(T) for i in range(len(T)): while stack and T[i] > T[stack[-1]]: peek = stack.pop(-1) ans[peek] = i - peek stack.append(i) return ans
JS:
var monostoneStack = function (T) { let stack = []; let result = []; for (let i = 0; i < T.length; i++) { result[i] = 0; while (stack.length > 0 && T[stack[stack.length - 1]] < T[i]) { let peek = stack.pop(); result[peek] = i - peek; } stack.push(i); } return result; };
下面幾個題幫助你理解單調棧, 並讓你明白何時能夠用單調棧進行算法優化。
單調棧本質就是棧, 棧自己就是一種受限的數據結構。其受限指的是隻能在一端進行操做。而單調棧在棧的基礎上進一步受限,即要求棧中的元素始終保持單調性。
因爲棧中都是單調的,所以其天生適合解決在其以後第一個小於其自己的位置的題目。你們若是遇到題目須要找在其以後第一個小於其自己的位置的題目,就但是考慮使用單調棧。
單調棧的寫法相對比較固定,你們能夠本身參考個人僞代碼本身總結一份模板,之後直接套用能夠大大提升作題效率和容錯率。
我整理的 1000 多頁的電子書已經開放下載了,你們能夠去個人公衆號《力扣加加》後臺回覆電子書獲取。
你們對此有何見解,歡迎給我留言,我有時間都會一一查看回答。更多算法套路能夠訪問個人 LeetCode 題解倉庫:https://github.com/azl3979858... 。 目前已經 37K star 啦。
你們也能夠關注個人公衆號《力扣加加》帶你啃下算法這塊硬骨頭。