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。

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

  1. /*
  2. * 本虛擬碼對應的是單調遞減棧
  3. *共n個元素,編號為0~n-1
  4. */
  5. while(棧為空) 棧頂元素出棧; //先清空棧
  6. a[n]=-1;
  7. for(i=0;i<=n;i++)
  8. {
  9.  if(棧為空或入棧元素大於等於棧頂元素) 入棧;
  10.  else
  11.  {
  12.   while(棧非空並且棧頂元素大於等於入棧元素)
  13.   {
  14.    棧頂元素出棧;
  15.    更新結果;
  16.   }
  17.   將最後一次出棧的棧頂元素(即當前元素可以拓展到的位置)入棧;
  18.   更新最後一次出棧的棧頂元素其對應的值;
  19.  }  
  20. }
  21. 輸出結果;

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

應用

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

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

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

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

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

應用1對應題目:

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

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

應用2對應題目:

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

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

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

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

應用3對應題目:

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

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