棧(stack) 是很簡單的⼀種資料結構, 先進後出的邏輯順序, 符合某些問
題的特點, ⽐如說函式調⽤棧。
單調棧實際上就是棧, 只是利⽤了⼀些巧妙的邏輯,使得每次新元素⼊棧
後, 棧內的元素都保持有序(單調遞增或單調遞減) 。
聽起來有點像堆(heap) ? 不是的, 單調棧⽤途不太⼴泛, 只處理⼀種典型
的問題, 叫做next greater element
。
⾸先, 講解next greater number
的原始問題:給你⼀個陣列, 返回⼀個等
⻓的陣列, 對應索引儲存著下⼀個更⼤元素, 如果沒有更⼤的元素, 就存-1。 不好⽤語⾔解釋清楚, 直接上⼀個例⼦:
給你⼀個陣列 [2,1,2,4,3], 你返回陣列 [4,2,4,-1,-1]。
解釋: 第⼀個 2 後⾯⽐ 2 ⼤的數是 4; 1 後⾯⽐ 1 ⼤的數是 2; 第⼆個 2 後⾯
⽐ 2 ⼤的數是 4; 4 後⾯沒有⽐ 4 ⼤的數, 填 -1; 3 後⾯沒有⽐ 3 ⼤的數, 填
-1。
這道題的暴⼒解法很好想到, 就是對每個元素後⾯都進⾏掃瞄, 找到第⼀個
更⼤的元素就⾏了。 但是暴⼒解法的時間複雜度是 o(n
2)o(n^2)
o(n2)。
這個問題可以這樣抽象思考:把陣列的元素想象成並列站⽴的⼈, 元素⼤⼩想象成⼈的⾝⾼。 這些⼈⾯對你站成⼀列, 如何求元素「2」 的 next greater number 呢? 很簡單, 如果能夠看到元素「2」 , 那麼他後⾯可⻅的第⼀個⼈就是「2」 的 next greater number, 因為⽐「2」 ⼩的元素⾝⾼不夠, 都被「2」 擋住了, 第⼀個露出來的就是答案。
這個情景很好理解吧? 帶著這個抽象的情景, 先來看下**。
vector<
int>
nextgreaterelement
(vector<
int>
& nums)
ans[i]
= s.
empty()
?-1: s.
top();
// 這個元素⾝後的第⼀個⾼個
s.push
(nums[i]);
// 進隊, 接受之後的⾝⾼判定吧!
}return ans;
}
這就是單調佇列解決問題的模板。 for 迴圈要從後往前掃瞄元素, 因為我們借助的是棧的結構, 倒著⼊棧, 其實是正著出棧。while 迴圈是把兩個「⾼個」元素之間的元素排除
, 因為他們的存在沒有意義, 前⾯擋著個「更⾼」的元素, 所以他們不可能被作為後續進來的元素的 next great number 了.
這個演算法的時間複雜度不是那麼直觀, 如果你看到 for 迴圈巢狀 while 迴圈, 可能認為這個演算法的複雜度也是 o(n
2)o(n^2)
o(n2
), 但是實際上這個演算法的複雜度只有
o(n
)o(n)
o(n)
分析它的時間複雜度, 要從整體來看: 總共有 n 個元素, 每個元素都被push ⼊棧了⼀次, ⽽最多會被 pop ⼀次, 沒有任何冗餘操作。 所以總的計算規模是和元素規模 n 成正⽐的, 也就是 o(n
)o(n)
o(n)
的複雜度
你已經掌握了單調棧的使⽤技巧, 來⼀個簡單的變形來加深⼀下理解:
給你⼀個陣列 t = [73, 74, 75, 71, 69, 72, 76, 73], 這個陣列存放的是近⼏天的天⽓⽓溫(這⽓溫是鐵板燒? 不是的, 這⾥⽤的華⽒度) 。 你返回⼀個陣列, 計算: 對於每⼀天, 你還要⾄少等多少天才能等到⼀個更暖和的⽓溫;如果等不到那⼀天, 填 0 。
舉例: 給你 t = [73, 74, 75, 71, 69, 72, 76, 73], 你返回 [1, 1, 4, 2, 1, 1, 0, 0]。
解釋: 第⼀天 73 華⽒度, 第⼆天 74 華⽒度, ⽐ 73 ⼤, 所以對於第⼀天,
只要等⼀天就能等到⼀個更暖和的⽓溫。 後⾯的同理
相同型別的問題, 相同的思路, 直接調⽤單調棧的演算法模板, 稍作改動就可以啦, 直接上**吧
vector<
int>
dailytemperatures
(vector<
int>
& t)
ans[i]
= s.
empty()
?0:(s.
top(
)- i)
;// 得到索引間距
s.push
(i);
// 加⼊索引, ⽽不是元素
}return ans;
}
同樣是 next greater number, 現在假設給你的陣列是個環形
的, 如何處理?
給你⼀個陣列 [2,1,2,4,3], 你返回陣列 [4,2,4,-1,4]。 擁有了環形屬性, 最後
⼀個元素 3 繞了⼀圈後找到了⽐⾃⼰⼤的元素 4 。
next greater number 的問題, 增加了環形屬性後, 問題的難點在於:這個 next 的意義不僅僅是當前元素的右邊了, 有可能出現在當前元素的左邊.
我們可以考慮這樣的思路
:將原始陣列「翻倍」, 就是在後⾯再接⼀個原始陣列
, 這樣的話, 按照之前「⽐⾝⾼」的流程, 每個元素不僅可以⽐較⾃⼰右邊的元素, ⽽且也可以和左邊的元素⽐較了。
怎麼實現呢? 你當然可以把這個雙倍⻓度的陣列構造出來,
然後套⽤演算法模板。 但是, 我們可以不⽤構造新陣列, ⽽是利⽤迴圈陣列的技巧來模擬
。 直接看**吧:
vector<
int>
nextgreaterelements
(vector<
int>
& nums)
return res;
}
演算法之單調棧與單調佇列
單調佇列顧名思義就是具有單一單調性的佇列。給定乙個數列,從左至右輸出每個長度為m的數列段內的最小數和最大數。數列長度 n 106,m n 數列為 6 4 10 10 8 6 4 2 12 14,求長度為3的數列段內的最大數,使用單調遞減棧。1 6,0 入隊,此時隊列為 6,0 2 4,1 入隊,此時...
單調棧 模板 單調棧模板
biu 單調棧主要用於求取左邊第乙個比它大,或者比它小的數。就比如站隊隨便排成一列,可以求到每個人後面第乙個比他高的人。同理可以推廣至右邊,比它矮均可。這就是單調遞增棧 遞減棧,從前至 棧,從後向前入棧的區別了。單調棧比較抽象,非常具有智慧型的想法,可應用的場景相當少,根據幾個經典題目體會它的用法會...
單調佇列 單調棧
參考文章 單調佇列 poj 2823 給定乙個數列,從左至右輸出每個長度為m的數列段內的最小數和最大數。數列長度 n 106 m n n 106,m n n 106 m n 直接暴力求解複雜度在0 mn 可以考慮維護區間最值,單調佇列則是維護區間佇列的強大 單調佇列的定義 單調佇列實現的大致過程 1...