【棧 && 單調棧】淺談單調棧與單調棧的理解

單調棧

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

實現:
先上結論:
利用單調棧,可以找到從左/右遍歷第一個比它小/大的元素的位置
一:
舉個例子:
假設有一個單調遞增的棧 S和一組數列:
a : 5 3 7 4

用數組L[i] 表示 第i個數向左遍歷的第一個比它小的元素的位置

如何求L[i]?

首先我們考慮一個樸素的算法,可以按順序枚舉每一個數,然後再依此向左遍歷。
但是當數列單調遞減時,複雜度是嚴格的O(n^2)。

此時我們便可以利用單調棧在O(n)的複雜度下實現

我們按順序遍歷數組,然後構造一個單調遞增棧

(1). i = 1時,因棧爲空,L[1] = 0,此時再將第一個元素的位置下標1存入棧中

此時棧中情況:
在這裏插入圖片描述

(2).i = 2時,因當前3小於棧頂元素對應的元素5,故將5彈出棧
此時棧爲空
故L[2] = 0
然後將元素3對應的位置下標2存入棧中

此時棧中情況:
在這裏插入圖片描述

(3).i = 3時,因當前7大於棧頂元素對應的元素3,故
L[3] = S.top() = 2 (棧頂元素的值)

然後將元素7對應的下標3存入棧
此時棧中情況:
在這裏插入圖片描述

(4).i = 4時,爲保持單調遞增的性質,應將棧頂元素3彈出
此時 L[4] = S.top() = 2;

然後將元素4對應的下標3存入棧
此時棧中情況:
在這裏插入圖片描述

至此 算法結束
對應的結果:
a : 5 3 7 4
L : 0 0 2 2

二:

例如實現一個單調遞增的棧,比如現在有一組數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(棧非空並且棧頂元素大於等於入棧元素)
		{
			棧頂元素出棧;
			更新結果; 
		} 
		將最後一次出棧的棧頂元素(即當前元素可以拓展到的位置)入棧; 
		更新最後一次出棧的棧頂元素其對應的值; 
	} 	 
}
輸出結果; 

將破壞棧單調性的元素都出棧後,最後一次出棧的元素就是當前入棧元素能拓展到的最左位置,更新其對應的值,並將其位置入棧。(注: 對於這句話到現在都沒有理解, 如果有大佬路過可以幫我解釋解釋。)

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

1.最基礎的應用就是給定一組數,針對每個數,尋找它和它右邊第一個比它大的數之間有多少個數。
2.給定一序列,尋找某一子序列,使得子序列中的最小值乘以子序列的長度最大。
3.給定一序列,尋找某一子序列,使得子序列中的最小值乘以子序列所有元素和最大。

對應題目:做的還不多,以後慢慢增加。
1、完美矩陣
這題選拔賽做的時候沒有寫出來,主要因爲太菜, 想到用單調棧,但沒有考慮的很全面,先說說我看到的第一感覺,用貪心,在每一層以高度遞增遍歷每一層,問題出在當時找到連續的就break了,沒有繼續往後找,不過以數據大小來說這樣寫一定會TLE的,之後想到單調棧,但以高度入棧了,寫的一團亂。直到學長講解的時候思路纔打開,應該以下標入棧。分別在左右找他的第一個小於他的數。就A了。這個實現不難就不貼下來了。後面再網上看到了兩個寫法,思路大致相同不過各有優劣。

-找出每一個單位寬度矩形的左邊界 l 和右邊界 r ,左邊界定義爲左邊連續的高度大於等於它的最左邊的矩形的下標,右邊界同理,從左往右推出所有的左邊界,從右往左推出所有的右邊界。
注意:矩形的高度h可以等於零,以爲這當左邊邊上的矩形高爲零或者右邊邊界上矩形高度爲零時,while循環無法停止,所以在while循環的條件中要加上限制邊界的條件。

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define max(a, b) ((a > b) ? (a) : (b))
long long h[100010],l[100010],r[100010];
int main()
{
    int n;
    scanf("%d",&n);
    int i, t;
    for(i=1; i<=n; i++)
    {
        scanf("%lld",&h[i]);
    }
    memset(l, 0, sizeof(l));
    memset(r, 0, sizeof(r));
    l[1]=1;
    r[n]=n;
    for(i=2; i<=n; i++)
    {
        t=i;
        while(t>1&&h[i]<=h[t-1])
            t=l[t-1];
        l[i]=t;
    }
    for(i=n-1; i>0; i--)
    {
        t=i;
        while(t<n&&h[i]<=h[t+1])
            t=r[t+1];
        r[i]=t;
    }
    long long s=0;
    for(i=1; i<=n; i++)
    {
        s = max(s, (r[i]-l[i]+1)*h[i]);
    }
    printf("%lld\n",s);
    return 0;
}
  • 利用根據單調遞增棧解決,如果棧爲空或入棧元素小於棧頂元素則入棧,否則會破壞棧的單調性,則將棧頂元素出棧並更新結果,直到棧爲空或碰到一個小於入棧元素的值。然後將當前元素入棧。這個方法還沒有徹底掌握,先貼代碼。
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<stack>
using namespace std;
typedef long long LL;
 
int main()
{
	int i,n,top; //top指向棧頂 
	stack<int> st; //棧用於保存矩形的編號,即位置 
	LL tmp,ans,a[100010]; //tmp爲臨時變量,記錄面積的值,ans爲結果,記錄最大面積值 
	while(~scanf("%d",&n)&&n)
	{
		for(i=0;i<n;i++)
			scanf("%lld",&a[i]);
		ans=0;
		a[n]=-1; //最後一個元素設爲最小值,以最後清空棧 
		for(i=0;i<=n;i++)
		{
			if(st.empty()||a[i]>=a[st.top()])
			{ //如果棧爲空或入棧元素大於等於棧頂元素 ,則入棧 
				st.push(i);
			}
			else
			{
				while(!st.empty()&&a[i]<a[st.top()])
				{ //如果棧非空且入棧元素小於棧頂元素,則將棧頂元素出棧 
					top=st.top();
					st.pop();
					tmp=(i-top)*a[top]; //在出棧過程中計算面積值 
					if(tmp>ans) ans=tmp; //更新面積最大值 
				}
				st.push(top); //只將可以延伸到的最左端的位置入棧
				a[top]=a[i];  //並修改該位置的值 
			}			
		}
		printf("%lld\n",ans);
	}
	return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章