1. 程式人生 > >單調棧的原理與應用

單調棧的原理與應用

定義:

單調棧,顧名思義,是棧內元素保持一定單調性(單調遞增或單調遞減)的棧。這裡的單調遞增或遞減是指的從棧頂到棧底單調遞增或遞減。既然是棧,就滿足後進先出的特點。與之相對應的是單調佇列。

實現:

例如實現一個單調遞增的棧,比如現在有一組數10,3,7,4,12。從左到右依次入棧,則如果棧為空或入棧元素值小於棧頂元素值,則入棧;否則,如果入棧則會破壞棧的單調性,則需要把比入棧元素小的元素全部出棧。單調遞減的棧反之。

10入棧時,棧為空,直接入棧,棧內元素為10。

3入棧時,棧頂元素10比3大,則入棧,棧內元素為10,3。

7入棧時,棧頂元素3比7小,則棧頂元素出棧,此時棧頂元素為10,比7大,則7入棧,棧內元素為10,7。

4入棧時,棧頂元素7比4大,則入棧,棧內元素為10,7,4。

12入棧時,棧頂元素4比12小,4出棧,此時棧頂元素為7,仍比12小,棧頂元素7繼續出棧,此時棧頂元素為10,仍比12小,10出棧,此時棧為空,12入棧,棧內元素為12。

至於程式碼的實現我覺得還是必須對應著題目去體會,也沒有太死板的模板,下面只給出虛擬碼吧。

/*
* 本虛擬碼對應的是單調遞減棧 
*共n個元素,編號為0~n-1
*/
while(棧為空) 棧頂元素出棧; //先清空棧
a[n]=-1;
for(i=0;i<=n;i++)
{
    if(棧為空或入棧元素大於等於棧頂元素) 入棧;
    else 
    {
        while(棧非空並且棧頂元素大於等於入棧元素)
        {
            棧頂元素出棧;
            更新結果; 
        } 
        將最後一次出棧的棧頂元素(即當前元素可以拓展到的位置)入棧; 
        更新最後一次出棧的棧頂元素其對應的值; 
    }      
}


輸出結果; 


PS:將破壞棧單調性的元素都出棧後,最後一次出棧的元素就是當前入棧元素能拓展到的最左位置,更新其對應的值,並將其位置入棧。

應用:

以上就是一個單調棧的定義及其實現,下面就來說一下它可以解決哪些問題。其實我也不能給出證明,以證明它為什麼能完成這些功能,只是簡單的把它的用途說一下,碰到問題時就需要大家靈活運用了。

1.最基礎的應用就是給定一組數,針對每個數,尋找它和它右邊第一個比它大的數之間有多少個數。

2.給定一序列,尋找某一子序列,使得子序列中的最小值乘以子序列的長度最大。

3.給定一序列,尋找某一子序列,使得子序列中的最小值乘以子序列所有元素和最大。

對應題目:下面先只給出對應的題目和大體思路,至於題解稍後完成後再一一補充。當然這只是現階段我自己碰到過的,可能不全,還請大家多多補充指正。

應用1對應題目:

1.POJ 3250

題意:有一群牛站成一排,每頭牛都是面朝右的,每頭牛可以看到他右邊身高比他小的牛。給出每頭牛的身高,要求每頭牛能看到的牛的總數。

思路:這也就是應用1所說的求每個數和它右邊第一個比它大的數之間的數的個數,分別求出後相加即可。樸素的做法是雙重迴圈遍歷,時間複雜度為O(n^2),用單調棧為O(n)。

題解:POJ 3250題解。

應用2對應題目:

1.POJ 2559

題意:有N個矩形,寬度都為1,給出N個矩形的高度,求由這N個矩形組成的圖形包含的最大的矩形面積。

思路:可以轉化為求區間最小值乘以區間長度的最大值。普通的思路是兩重迴圈遍歷,時間複雜度為O(n^2),用單調棧為O(n)。

題解:POJ 2559 題解。

2.POJ 3494

題意:求僅由0,1組成的矩陣中,全部由1組成的小矩陣的最大面積。

思路:這個是上一題POJ 2559的升級版,把一維的操作變成二維的即可。這之前需要一個預處理。

題解:POJ 3494題解。

應用3對應題目:

1.POJ 2796

題意:給出一個序列,求出一個子序列,使得這個序列中的最小值乘以這個序列的和的值最大。

思路:直接用單調棧解決即可,由於維護單調棧的過程中會改變原陣列的值,所以需要加一個sum陣列儲存字首和,也方便計算區間元素和。

題解:POJ 2796題解。
--------------------- 
作者:棉花糖灬 
來源:CSDN 
原文:https://blog.csdn.net/zuzhiang/article/details/78134247 
版權宣告:本文為博主原創文章,轉載請附上博文連結!