詳解--單調隊列 經典滑動窗口問題


單調隊列,即單調的隊列。使用頻率不高,但在有些程序中會有非同尋常的作用。

動態規划·單調隊列的理解

動態規划時常常會見到形如這樣的轉移方程:
f[x] = max or min{g(k) | b[x] <= k < x} + w[x]
(其中b[x]隨x單調不降,即b[1]<=b[2]<=b[3]<=...<=b[n])
(g[k]表示一個和k或f[k]有關的函數,w[x]表示一個和x有關的函數)
這個方程怎樣求解呢?我們注意到這樣一個性質:如果存在兩個數j, k,使得j <= k,而且g(k) <= g(j),則決策j是毫無用處的。因為根據b[x]單調的特性,如果j可以作為合法決策,那么k一定可以作為合法決策,並且k是一個比j要優的決策。因為k比j要優,(注意:在這個經典模型中,“優”是絕對的,是與當前正在計算的狀態無關的),所以,如果把待 決策表中的決策按照k排序的話,則g(k)必然是不降的。在此例中決策表即f[x].
這樣,就引導我們使用一個單調隊列來維護決策表。對於每一個狀態f(x)來說,計算過程分為以下幾步:
1、 隊首元素出隊,直到隊首元素在給定的范圍中。
2、 此時,隊首元素就是狀態f(x)的最優決策,
3、 計算g(x),並將其插入到單調隊列的尾部,同時維持隊列的 單調性(不斷地出隊,直到隊列單調為止)。
重復上述步驟直到所有的 函數值均被計算出來。不難看出這樣的算法均攤 時間復雜度是O(1)的。因此求解f(x)的時間復雜度從O(n^2)降到了O(n)。

以下來自某博客+自己補充:

我們從最簡單的問題開始:

給定一個長度為N的整數數列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的窗在整數數列上移動,求窗里面所包含的數的最大值。

解法一:

很直觀的一種解法,那就是從數列的開頭,將窗放上去,然后找到這最開始的k個數的最大值,然后窗最后移一個單元,繼續找到k個數中的最大值。

這種方法每求一個f(i),都要進行k-1次的比較,復雜度為O(N*k)。

那么有沒有更快一點的算法呢?

解法二:

我們知道,上一種算法有一個地方是重復比較了,就是在找當前的f(i)的時候,i的前面k-1個數其它在算f(i-1)的時候我們就比較過了。那么我們能不能保存上一次的結果呢?當然主要是i的前k-1個數中的最大值了。答案是可以,這就要用到單調遞減隊列。

單調遞減隊列是這么一個隊列,它的頭元素一直是隊列當中的最大值,而且隊列中的值是按照遞減的順序排列的。我們可以從隊列的末尾插入一個元素,可以從隊列的兩端刪除元素。

1.首先看插入元素:為了保證隊列的遞減性,我們在插入元素v的時候,要將隊尾的元素和v比較,如果隊尾的元素不大於v,則刪除隊尾的元素,然后繼續將新的隊尾的元素與v比較,直到隊尾的元素大於v,這個時候我們才將v插入到隊尾。

2.隊尾的刪除剛剛已經說了,那么隊首的元素什么時候刪除呢?由於我們只需要保存i的前k-1個元素中的最大值,所以當隊首的元素的索引或下標小於i-k+1的時候,就說明隊首的元素對於求f(i)已經沒有意義了,因為它已經不在窗里面了。所以當index[隊首元素]<i-k+1時,將隊首元素刪除。

(補充:隊列中的元素主要包括了兩個性質:大小--決定它是否影響f[i]的求值,時效性(刪除隊頭使用)--數組下標決定它是否已經離開了滑動窗口,不在影響f[i]了,刪除隊尾時,是新入隊的時效性>已在隊中的元素了,如果隊尾的取值不如新入隊的元素的值優的話,那么就可以刪除隊尾了。)

從上面的介紹當中,我們知道,單調隊列與隊列唯一的不同就在於它不僅要保存元素的值,而且要保存元素的索引(當然在實際應用中我們可以只需要保存索引,而通過索引間接找到當前索引的值)。

為了讓讀者更明白一點,我舉個簡單的例子。

假設數列為:8,7,12,5,16,9,17,2,4,6.N=10,k=3.

那么我們構造一個長度為3的單調遞減隊列:

首先,那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步時隊列當中的首元素: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 ≤ 10 6 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.
Window position Minimum value Maximum value
[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 7

Sample 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的這個數,代替了本來的最大值或者最小值,所以一開始時會有mintail<0,但是之后馬上就++mintail了,沒有訪問數組,所以不會有越界情況的。*/
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 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM