详解--单调队列 经典滑动窗口问题
單調(diào)隊列,即單調(diào)的隊列。使用頻率不高,但在有些程序中會有非同尋常的作用。
動態(tài)規(guī)劃·單調(diào)隊列的理解
做動態(tài)規(guī)劃時常常會見到形如這樣的轉(zhuǎn)移方程: f[x] = max or min{g(k) | b[x] <= k < x} + w[x] (其中b[x]隨x單調(diào)不降,即b[1]<=b[2]<=b[3]<=...<=b[n]) (g[k]表示一個和k或f[k]有關(guān)的函數(shù),w[x]表示一個和x有關(guān)的函數(shù)) 這個方程怎樣求解呢?我們注意到這樣一個性質(zhì):如果存在兩個數(shù)j, k,使得j <= k,而且g(k) <= g(j),則決策j是毫無用處的。因為根據(jù)b[x]單調(diào)的特性,如果j可以作為合法決策,那么k一定可以作為合法決策,并且k是一個比j要優(yōu)的決策。因為k比j要優(yōu),(注意:在這個經(jīng)典模型中,“優(yōu)”是絕對的,是與當(dāng)前正在計算的狀態(tài)無關(guān)的),所以,如果把待決策表中的決策按照k排序的話,則g(k)必然是不降的。在此例中決策表即f[x]. 這樣,就引導(dǎo)我們使用一個單調(diào)隊列來維護(hù)決策表。對于每一個狀態(tài)f(x)來說,計算過程分為以下幾步: 1、 隊首元素出隊,直到隊首元素在給定的范圍中。 2、 此時,隊首元素就是狀態(tài)f(x)的最優(yōu)決策, 3、 計算g(x),并將其插入到單調(diào)隊列的尾部,同時維持隊列的單調(diào)性(不斷地出隊,直到隊列單調(diào)為止)。 重復(fù)上述步驟直到所有的函數(shù)值均被計算出來。不難看出這樣的算法均攤時間復(fù)雜度是O(1)的。因此求解f(x)的時間復(fù)雜度從O(n^2)降到了O(n)。以下來自某博客+自己補(bǔ)充:
我們從最簡單的問題開始:
給定一個長度為N的整數(shù)數(shù)列a(i),i=0,1,...,N-1和窗長度k.
要求:
????? f(i) = max{a(i-k+1),a(i-k+2),..., a(i)},i = 0,1,...,N-1
問題的另一種描述就是用一個長度為k的窗在整數(shù)數(shù)列上移動,求窗里面所包含的數(shù)的最大值。
解法一:
很直觀的一種解法,那就是從數(shù)列的開頭,將窗放上去,然后找到這最開始的k個數(shù)的最大值,然后窗最后移一個單元,繼續(xù)找到k個數(shù)中的最大值。
這種方法每求一個f(i),都要進(jìn)行k-1次的比較,復(fù)雜度為O(N*k)。
那么有沒有更快一點的算法呢?
解法二:
我們知道,上一種算法有一個地方是重復(fù)比較了,就是在找當(dāng)前的f(i)的時候,i的前面k-1個數(shù)其它在算f(i-1)的時候我們就比較過了。那么我們能不能保存上一次的結(jié)果呢?當(dāng)然主要是i的前k-1個數(shù)中的最大值了。答案是可以,這就要用到單調(diào)遞減隊列。
單調(diào)遞減隊列是這么一個隊列,它的頭元素一直是隊列當(dāng)中的最大值,而且隊列中的值是按照遞減的順序排列的。我們可以從隊列的末尾插入一個元素,可以從隊列的兩端刪除元素。
1.首先看插入元素:為了保證隊列的遞減性,我們在插入元素v的時候,要將隊尾的元素和v比較,如果隊尾的元素不大于v,則刪除隊尾的元素,然后繼續(xù)將新的隊尾的元素與v比較,直到隊尾的元素大于v,這個時候我們才將v插入到隊尾。
2.隊尾的刪除剛剛已經(jīng)說了,那么隊首的元素什么時候刪除呢?由于我們只需要保存i的前k-1個元素中的最大值,所以當(dāng)隊首的元素的索引或下標(biāo)小于i-k+1的時候,就說明隊首的元素對于求f(i)已經(jīng)沒有意義了,因為它已經(jīng)不在窗里面了。所以當(dāng)index[隊首元素]<i-k+1時,將隊首元素刪除。
(補(bǔ)充:隊列中的元素主要包括了兩個性質(zhì):大小--決定它是否影響f[i]的求值,時效性(刪除隊頭使用)--數(shù)組下標(biāo)決定它是否已經(jīng)離開了滑動窗口,不在影響f[i]了,刪除隊尾時,是新入隊的時效性>已在隊中的元素了,如果隊尾的取值不如新入隊的元素的值優(yōu)的話,那么就可以刪除隊尾了。)
從上面的介紹當(dāng)中,我們知道,單調(diào)隊列與隊列唯一的不同就在于它不僅要保存元素的值,而且要保存元素的索引(當(dāng)然在實際應(yīng)用中我們可以只需要保存索引,而通過索引間接找到當(dāng)前索引的值)。
為了讓讀者更明白一點,我舉個簡單的例子。
假設(shè)數(shù)列為:8,7,12,5,16,9,17,2,4,6.N=10,k=3.
那么我們構(gòu)造一個長度為3的單調(diào)遞減隊列:
首先,那8和它的索引0放入隊列中,我們用(8,0)表示,每一步插入元素時隊列中的元素如下:
0:插入8,隊列為:(8,0)
1:插入7,隊列為:(8,0),(7,1)
2:插入12,隊列為:(12,2)
3:插入5,隊列為:(12,2),(5,3)
4:插入16,隊列為:(16,4)
5:插入9,隊列為:(16,4),(9,5)
。。。。依此類推
那么f(i)就是第i步時隊列當(dāng)中的首元素:8,8,12,12,16,16,。。。
例題:POJ 2823 滑動窗口
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Sliding Window
| Time Limit:?12000MS | ? | Memory Limit:?65536K |
| Total Submissions:?54158 | ? | Accepted:?15543 |
| Case Time Limit:?5000MS | ||
Description
An array of size?n?≤ 106?is given to you. There is a sliding window of size?k?which is moving from the very left of the array to the very right. You can only see the?k?numbers in the window. Each time the sliding window moves rightwards by one position. Following is an example:?The array is?[1?3?-1?-3?5?3?6?7], and?k?is 3.
| [1??3??-1]?-3??5??3??6??7? | -1 | 3 |
| ?1?[3??-1??-3]?5??3??6??7? | -3 | 3 |
| ?1??3?[-1??-3??5]?3??6??7? | -3 | 5 |
| ?1??3??-1?[-3??5??3]?6??7? | -3 | 5 |
| ?1??3??-1??-3?[5??3??6]?7? | 3 | 6 |
| ?1??3??-1??-3??5?[3??6??7] | 3 | 7 |
Your task is to determine the maximum and minimum values in the sliding window at each position.?
Input
The input consists of two lines. The first line contains two integers?n?and?k?which are the lengths of the array and the sliding window. There are?n?integers in the second line.?Output
There are two lines in the output. The first line gives the minimum values in the window at each position, from left to right, respectively. The second line gives the maximum values.?Sample Input
8 3 1 3 -1 -3 5 3 6 7Sample Output
-1 -3 -3 -3 3 3 3 3 5 5 6 7 1 #define N 1000005 2 #include<iostream> 3 #include<cstring> 4 using namespace std; 5 #include<cstdio> 6 struct Pai{ 7 int val,pos; 8 }; 9 Pai minque[N],maxque[N]; 10 int minans[N],maxans[N],minhead,maxhead,mintail,maxtail,cur=0; 11 int n,k; 12 int main() 13 { 14 scanf("%d%d",&n,&k); 15 int num; 16 minque[0].val=(1<<31)-1; 17 maxque[0].val=(1<<31)-1; 18 maxque[0].val*=-1; 19 /*使用最大值最小值,為了讓que[0]會被后來的元素占用,防止因為que[0]=0的這個數(shù),代替了本來的最大值或者最小值,所以一開始時會有mintail<0,但是之后馬上就++mintail了,沒有訪問數(shù)組,所以不會有越界情況的。*/ 20 for(int i=1;i<=k;++i) 21 { 22 scanf("%d",&num); 23 while(minhead<=mintail&&minque[mintail].val>=num) mintail--; 24 minque[++mintail].val=num; 25 minque[mintail].pos=i; 26 while(maxhead<=maxtail&&maxque[maxtail].val<=num) maxtail--; 27 maxque[++maxtail].val=num; 28 maxque[maxtail].pos=i; 29 } 30 for(int i=k+1;i<=n;++i) 31 { 32 minans[++cur]=minque[minhead].val; 33 maxans[cur]=maxque[maxhead].val; 34 scanf("%d",&num); 35 36 while(minhead<=mintail&&i-minque[minhead].pos>=k)++minhead; 37 while(minhead<=mintail&&minque[mintail].val>=num) mintail--; 38 minque[++mintail].val=num; 39 minque[mintail].pos=i; 40 41 while(maxhead<=maxtail&&i-maxque[maxhead].pos>=k)++maxhead; 42 while(maxhead<=maxtail&&maxque[maxtail].val<=num) maxtail--; 43 maxque[++maxtail].val=num; 44 maxque[maxtail].pos=i; 45 } 46 minans[++cur]=minque[minhead].val; 47 maxans[cur]=maxque[maxhead].val; 48 for(int i=1;i<=cur;++i) 49 printf("%d ",minans[i]); 50 printf("\n"); 51 for(int i=1;i<=cur;++i) 52 printf("%d ",maxans[i]); 53 return 0; 54 }?
轉(zhuǎn)載于:https://www.cnblogs.com/c1299401227/p/5774133.html
總結(jié)
以上是生活随笔為你收集整理的详解--单调队列 经典滑动窗口问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 广度优先搜索(BFS)
- 下一篇: (HY000): Cannot modi