单调栈单调队列入门
單調(diào)隊(duì)列是什么呢?可以直接從問題開始來展開。
Poj 2823
給定一個(gè)數(shù)列,從左至右輸出每個(gè)長度為m的數(shù)列段內(nèi)的最小數(shù)和最大數(shù)。
數(shù)列長度:\(N <=10^6 ,m<=N\)
解法①
很直觀的一種解法,那就是從數(shù)列的開頭,將窗放上去,然后找到這最開始的k個(gè)數(shù)的最大值,然后窗最后移一個(gè)單元,繼續(xù)找到k個(gè)數(shù)中的最大值。
這種方法每求一個(gè)f(i),都要進(jìn)行k-1次的比較,復(fù)雜度為$ O(Nk) $。
顯然,如果暴力時(shí)間復(fù)雜度為 $ O(Nm) $ 不超時(shí)就怪了。
解法②
還有一種想法是維護(hù)一個(gè)BST,然后for循環(huán)從左到右,依次加入到BST里面,如果某個(gè)數(shù)超出了k的范圍,就從BST中刪除。
因?yàn)槊總€(gè)數(shù)只insert一次,最多erase一次,所以復(fù)雜度是\(O(NlogN)\)的,已經(jīng)很不錯(cuò)了。
但是\(10^6\)級(jí)別的極限數(shù)據(jù),這種做法會(huì)被卡掉的,況且維護(hù)一個(gè)BST的代碼也比較麻煩。
解法③
我們知道,解法①在暴力枚舉的過程中,有一個(gè)地方是重復(fù)比較了,就是在找當(dāng)前的f(i)的時(shí)候,i的前面其它m-1個(gè)數(shù)在算f(i-1)的時(shí)候我們就比較過了。
當(dāng)你一個(gè)個(gè)往下找時(shí),每一次都是少一個(gè)然后多一個(gè),如果少的不是最大值,然后再問新加進(jìn)來的,看起來很省時(shí)間對(duì)吧,那么如果少了的是最大值呢?第二個(gè)最大值是什么??
那么我們能不能保存上一次的結(jié)果呢?當(dāng)然主要是i的前k-1個(gè)數(shù)中的最大值了。答案是可以,這就要用到單調(diào)隊(duì)列。
對(duì)于單調(diào)隊(duì)列,我們這樣子來定義:
- 1、維護(hù)區(qū)間最值
- 2、去除冗雜狀態(tài) 如上題,區(qū)間中的兩個(gè)元素a[i],a[j](假設(shè)現(xiàn)在再求最大值)
若 j>i且a[j]>=a[i] ,a[j]比a[i]還大而且還在后面(目前a[j]留在隊(duì)列肯定比a[i]有用,因?yàn)槟闶峭笸?#xff0c; 核心思想 !!!) - 3、保持隊(duì)列單調(diào),最大值是單調(diào)遞減序列,最小值反之
- 4、最優(yōu)選擇在隊(duì)首
單調(diào)隊(duì)列實(shí)現(xiàn)的大致過程:
1、維護(hù)隊(duì)首(對(duì)于上題就是如果隊(duì)首已經(jīng)是當(dāng)前元素的m個(gè)之前,則隊(duì)首就應(yīng)該被刪了,head++)
2、在隊(duì)尾插入(每插入一個(gè)就要從隊(duì)尾開始往前去除冗雜狀態(tài),保持單調(diào)性)
簡單舉例應(yīng)用
數(shù)列為:6 4 10 10 8 6 4 2 12 14
N=10,K=3;
那么我們構(gòu)造一個(gè)長度為3的單調(diào)遞減隊(duì)列:
首先,那6和它的位置0放入隊(duì)列中,我們用(6,0)表示,每一步插入元素時(shí)隊(duì)列中的元素如下
插入6:(6,0);
插入4:(6,0),(4,1);
插入10:(10,2);
插入第二個(gè)10,保留后面那個(gè):(10,3);
插入8:(10,3),(8,4);
插入6:(10,3),(8,4),(6,5);
插入4,之前的10已經(jīng)超出范圍所以排掉:(8,4),(6,5),(4,6);
插入2,同理:(6,5),(4,6),(2,7);
插入12:(12,8);
插入14:(14,9);
那么f(i)就是第i步時(shí)隊(duì)列當(dāng)中的首元素:6,6,10,10,10,10,8,6,12,14
同理,最小值也可以用單調(diào)隊(duì)列來做。
單調(diào)隊(duì)列的時(shí)間復(fù)雜度是O(N),因?yàn)槊總€(gè)數(shù)只會(huì)進(jìn)隊(duì)和出隊(duì)一次,所以這個(gè)算法的效率還是很高的。
注意:建議直接用數(shù)組模擬單調(diào)隊(duì)列,因?yàn)橄到y(tǒng)自帶容器不方便而且不易調(diào)試,同時(shí),每個(gè)數(shù)只會(huì)進(jìn)去一次,所以,數(shù)組絕對(duì)不會(huì)爆,空間也是S(N),優(yōu)于堆或線段樹等數(shù)據(jù)結(jié)構(gòu)。
更重要的:單調(diào)是一種思想,當(dāng)我們解決問題的時(shí)候發(fā)現(xiàn)有許多冗雜無用的狀態(tài)時(shí),我們可以采用單調(diào)思想,用單調(diào)隊(duì)列或類似于單調(diào)隊(duì)列的方法去除冗雜狀態(tài),保存我們想要的狀態(tài),
#include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; struct node {int x,y; }v[1010000]; //x表示值,y表示位置 可以理解為下標(biāo) int a[1010000],n,m,mx[1010000],mn[1010000]; void getmin() {int i,head=1,tail=0;// 默認(rèn)起始位置為1 因?yàn)椴迦胧莢[++tail]故初始化為0for(i=1;i<m;i++){while(head<=tail && v[tail].x>=a[i]) tail--;v[++tail].x=a[i],v[tail].y=i;// 根據(jù)題目 前m-1個(gè)先直接進(jìn)入隊(duì)列}for(;i<=n;i++){while(head<=tail && v[tail].x>=a[i]) tail--;v[++tail].x=a[i],v[tail].y=i;while(v[head].y<i-m+1) head++;mn[i-m+1]=v[head].x;// 道理同上,當(dāng)然了 要把已經(jīng)超出范圍的從head開始排出// 然后每個(gè)隊(duì)首則是目前m個(gè)數(shù)的最小值} } void getmax() //最大值同最小值的道理,只不過是維護(hù)的是遞減隊(duì)列 {int i,head=1,tail=0;for(i=1;i<m;i++){while(head<=tail && v[tail].x<=a[i]) tail--;v[++tail].x=a[i],v[tail].y=i;}for(;i<=n;i++){while(head<=tail && v[tail].x<=a[i]) tail--;v[++tail].x=a[i],v[tail].y=i;while(v[head].y<i-m+1) head++;mx[i-m+1]=v[head].x;} } int main() {int i,j;scanf("%d%d",&n,&m);for(i=1;i<=n;i++)scanf("%d",&a[i]);getmin();getmax();for(i=1;i<=n-m+1;i++){if(i==1)printf("%d",mn[i]);else printf(" %d",mn[i]);}printf("\n");for(i=1;i<=n-m+1;i++){if(i==1)printf("%d",mx[i]);else printf(" %d",mx[i]);}printf("\n");return 0; }這就是單調(diào)隊(duì)列,單調(diào)棧和單調(diào)隊(duì)列區(qū)別不大,都是每次push的時(shí)候在棧頂要維護(hù)單調(diào)性。
關(guān)于單調(diào)棧的一道題目
問題描述
地上從左到右豎立著 n 塊木板,從 1 到 n 依次編號(hào),如下圖所示。我們知道每塊木板的高度,在第 n 塊木板右側(cè)豎立著一塊高度無限大的木板,現(xiàn)對(duì)每塊木板依次做如下的操作:對(duì)于第 i 塊木板,我們從其右側(cè)開始倒水,直到水的高度等于第 i 塊木板的高度,倒入的水會(huì)淹沒 ai 塊木板(如果木板左右兩側(cè)水的高度大于等于木板高度即視為木板被淹沒),求 n 次操作后,所有 ai 的和是多少。如圖上所示,在第 4 塊木板右側(cè)倒水,可以淹沒第 5 塊和第 6 塊一共 2 塊木板,a4 = 2。
解法①
暴力求解,復(fù)雜度是O(n2)
例如現(xiàn)在存在5塊木板
每塊木板從左至右高分別為
10,5,8,12,6
從第一塊木板(高度為10)右側(cè)開始倒水,當(dāng)水到達(dá)第四塊木板(高度為12)時(shí),可以淹沒第一塊木板
即第一塊木板至第四塊木板之間的木板數(shù)量,即4-1-1 = 2,a1 = 2;
也就是說:尋找在第 i 個(gè)木板右邊第一個(gè)比它大的木板j,ai 就等于木板 i 和木板 j 之間的木板數(shù)
同理得到
a2=0
a3=0
a4=1
a5=0
sum = a1 + a2 +a3 +a4 +a5 = 3
于是,問題就變成了尋找在第 i 個(gè)數(shù)右邊第一個(gè)比它大的數(shù)??梢员┝η蠼?#xff0c;從 1 循環(huán)到 n,對(duì)每塊木板再往右循環(huán)一遍,這樣的時(shí)間復(fù)雜度是\(O(n2)\) 。
解法②
單調(diào)棧來求解的話,復(fù)雜度是O(n)
結(jié)合單調(diào)棧的性質(zhì):使用單調(diào)棧可以找到元素向左遍歷第一個(gè)比他小的元素,也可以找到元素向左遍歷第一個(gè)比他大的元素。
顧名思義,單調(diào)棧就是棧內(nèi)元素單調(diào)遞增或者單調(diào)遞減的棧,這一點(diǎn)和單調(diào)隊(duì)列很相似,但是單調(diào)棧只能在棧頂操作。
單調(diào)棧有以下兩個(gè)性質(zhì):
1、若是單調(diào)遞增棧,則從棧頂?shù)綏5椎脑厥菄?yán)格遞增的。若是單調(diào)遞減棧,則從棧頂?shù)綏5椎脑厥菄?yán)格遞減的。
2、越靠近棧頂?shù)脑卦胶筮M(jìn)棧。
單調(diào)棧與單調(diào)隊(duì)列不同的地方在于棧只能在棧頂操作,因此一般在應(yīng)用單調(diào)棧的地方不限定棧的大小,否則可能會(huì)造成元素?zé)o法進(jìn)棧。
元素進(jìn)棧過程:對(duì)于單調(diào)遞增棧,若當(dāng)前進(jìn)棧元素為e,從棧頂開始遍歷元素,把小于e或者等于e的元素彈出棧,直接遇到一個(gè)大于e的元素或者棧為空為止,然后再把e壓入棧中。對(duì)于單調(diào)遞減棧,則每次彈出的是大于e或者等于e的元素。
數(shù)據(jù)模擬木板倒水單調(diào)棧的入棧計(jì)算過程
思路:尋找比棧頂高的木板i,找到就出棧,不是就把木板i入棧,給出循環(huán)計(jì)數(shù)樣例 10,5,8,12,6
從左往右掃描
棧為空,10入棧 棧:10 此時(shí)棧頂是10,也就是說要尋找比10大的木板
5比10小,5入棧 棧:5,10 此時(shí)棧頂是5,也就是說要尋找比5大的木板
8比5大,5出棧 棧:10
這個(gè)時(shí)候,第二個(gè)高度為5的木板右邊比它高的木板已經(jīng)找到了,是第三個(gè)木板8,所以5出棧,計(jì)算a2 = 3-2-1 = 0
8比10小,8入棧 棧:8,10 此時(shí)棧頂是8,也就是說要尋找比8大的木板
12比8大,8出棧 棧:10
第三個(gè)高度為8的木板右邊比它高的木板已經(jīng)找到了,是第四個(gè)木板12,8出棧,計(jì)算a3 = 4-3-1 = 0
12比10大,10出棧 棧:空
第一個(gè)高度為10的木板右邊比它高的木板已經(jīng)找到了,是第四個(gè)木板12,所以10出棧,計(jì)算a1 = 4-1-1 = 2
棧為空,12入棧 棧:12 此時(shí)棧頂是12,也就是說要尋找比12大的木板
6比12小,6入棧 棧:6,12 此時(shí)棧頂是6,也就是說要尋找比6大的木板
掃描完成結(jié)束
最后棧的結(jié)構(gòu)是:6,12 棧頂為6
由于最右端豎立著一塊高度無限大的木板,即存在第六塊木板高度為無窮,所以剩余兩塊木板的算法如下 a5 = 6-5-1 =0
a4 = 6-4-1 = 1
sum = a1 + a2 +a3 +a4 +a5 = 3
因此本題可以在\(O(n)\)的時(shí)間內(nèi)迎刃而解了。
從左往右將木板節(jié)點(diǎn)壓棧,遇到比棧頂木板高的木板就將當(dāng)前棧頂木板出棧并計(jì)算淹沒的木板數(shù),如此循環(huán)直到棧頂木板高度比當(dāng)前木板高或者棧為空,然后將此木板壓棧。木板全都?jí)簵M瓿珊?#xff0c;棧內(nèi)剩余的木板都是右側(cè)沒有比它們更高的木板的,所以一個(gè)個(gè)出棧并計(jì)算ai=n+1-temp_id-1(用最右邊無限高的木板減)。
轉(zhuǎn)載于:https://www.cnblogs.com/tham/p/8038828.html
總結(jié)
- 上一篇: Sublime Text3 配置设置攻略
- 下一篇: 03.openssl-获得支持加密算法