[POJ-2823] -Sliding Window
Sliding Window
Time Limit: 12000MS Memory Limit: 65536K
Total Submissions: 56028 Accepted: 16112
Case Time Limit: 5000MS
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
Output
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
题意: 给定一行数,共N个。有一个长度为K的窗口从左向右滑动,窗口中始终有K个数字,窗口每次滑动一个数字。求各个时刻窗口中的最大值和最小值。
一、概念介绍
1、 双端队列
双端队列是一种线性表,是一种特殊的队列,遵守先进先出的原则。双端队列支持以下4种操作:
(1) 从队首删除
(2) 从队尾删除
(3) 从队尾插入
(4) 查询线性表中任意一元素的值
2、 单调队列
单调队列是一种特殊的双端队列,其内部元素具有单调性。最大队列与最小队列是两种比较常用的单调队列,其内部元素分别是严格单调递减(不是非递增)和严格单调递增(不是非递减)的。
单调队列的常用操作如下:
(1) 插入:若新元素从队尾插入后会破坏单调性,则删除队尾元素,直到插入后不再破坏单调性为止,再将其插入单调队列。
(2) 获取最优(最大、最小)值:访问队首元素
以下是一个单调递增队列的例子:
队列大小不能超过3,入队元素依次为3,2,8,4,5,7,6,4
3入队:(3)
3从队尾出队,2入队:(2)
8入队:(2,8)
8从队尾出队,4入队:(2,4)
5入队:(2,4,5)
2从队头出队,7入队:(4,5,7)
7从队尾出队,6入队:(4,5,6)
6从队尾出队,5从队尾出队,4从队尾出队,4入队:(4)
以上左端为队头,右端为队尾。
二、单调队列的应用
1、最大值的维护:
比如我们要维护一个区间为k的最大值的单调队列,由于新插入 的节点他的“生命力”肯定比原先已经在队列中的元素“活”的时间长,将插入元素不断与队尾元素比, 如果他大于队尾元素,那么tail--将队尾元素删掉,(因为目前插入的这个元素值(设为pos)更大,而且“活”的时间 长,有pos在,队尾元素的有“生”之年永远都没法为最大值,故而直接无视比pos小的队尾了)。直到对空位置或者 找到了一个比pos大的队尾。
这个问题相当于一个数据流(数列a)在不断地到来,而数据是不断过期的,相当于我们只能保存有限的数据(sliding window中的数据,此题中就是窗口的宽度w),对于到来的查询(此题中查询是每时刻都有的),我们要返回当前滑动窗口中的最大值最小值。注意,元素是不断过期的。
解决这个问题可以使用一种叫做单调队列的数据结构,它维护这样一种队列:
a)从队头到队尾,元素在我们所关注的指标下是递减的(严格递减,而不是非递增),比如查询如果每次问的是窗口内的最小值,那么队列中元素从左至右就应该递增,如果每次问的是窗口内的最大值,则应该递减,依此类推。这是为了保证每次查询只需要取队头元素。
b)从队头到队尾,元素对应的时刻(此题中是该元素在数列a中的下标)是递增的,但不要求连续,这是为了保证最左面的元素总是最先过期,且每当有新元素来临的时候一定是插入队尾。
满足以上两点的队列就是单调队列,首先,只有第一个元素的序列一定是单调队列。
那么怎么维护这个单调队列呢?无非是处理插入和查询两个操作。
对于插入,由于性质b,因此来的新元素插入到队列的最后就能维持b)继续成立。但是为了维护a)的成立,即元素在我们关注的指标下递减,从队尾插入新元素的时候可能要删除队尾的一些元素,具体说来就是,找到第一个大于(在所关注指标下)新元素的元素,删除其后所有元素,并将新元素插于其后。因为所有被删除的元素都比新元素要小,而且比新元素要旧,因此在以后的任何查询中都不可能成为答案,所以可以放心删除。
对于查询,由于性质b,因此所有该时刻过期的元素一定都集中在队头,因此利用查询的时机删除队头所有过期的元素,在不含过期元素后,队头得元素就是查询的答案(性质a),将其返回即可。由于每个元素都进队出队一次,因此摊销复杂度为O(n)。
模板:
1 #include <iostream> 2 #include <algorithm> 3 #include <cstdio> 4 #include <cstring> 5 using namespace std; 6 const int maxn = 1e6 + 5; 7 struct node 8 { 9 int pos, val; 10 }q[maxn]; 11 int k, num[maxn], n, Min[maxn], Max[maxn]; 12 void getmin() 13 { 14 int head = 1, tail = 0; 15 for(int i = 1; i < k; i++) 16 { 17 while(head <= tail && num[i] < q[tail].val) tail--; 18 tail++; 19 q[tail].val = num[i]; 20 q[tail].pos = i; 21 } 22 for(int i = k; i <= n; i++) 23 { 24 while(head <= tail && num[i] < q[tail].val) tail--; 25 tail++; 26 q[tail].val = num[i]; 27 q[tail].pos = i; 28 while(i-q[head].pos >= k) head++; 29 Min[i-k] = q[head].val; 30 } 31 } 32 void getmax() 33 { 34 int head = 1, tail = 0; 35 for(int i = 1; i < k; i++) 36 { 37 while(head <= tail && num[i] > q[tail].val) tail--; 38 tail++; 39 q[tail].val = num[i]; 40 q[tail].pos = i; 41 } 42 for(int i = k; i <= n; i++) 43 { 44 while(head <= tail && num[i] > q[tail].val) tail--; 45 tail++; 46 q[tail].val = num[i]; 47 q[tail].pos = i; 48 while(i - q[head].pos >= k) head++; 49 Max[i-k] = q[head].val; 50 } 51 52 } 53 int main() 54 { 55 while(~scanf("%d%d", &n, &k)) 56 { 57 for(int i = 1; i <= n; i++) scanf("%d", &num[i]); 58 getmin(); 59 for(int i=0; i<=n-k; i++) printf("%d%c",Min[i],i==n-k?'\n':' '); 60 getmax(); 61 for(int i=0; i<=n-k; i++) printf("%d%c",Max[i],i==n-k?'\n':' '); 62 } 63 return 0; 64 }
用优先队列写的:操作的是下标
1 #include<algorithm> 2 #include<queue> 3 #include<vector> 4 using namespace std; 5 int a[1000011];//数组数据 6 7 int OutMin[1000011];//最小值 8 9 int OutMax[1000011];//最大值 10 11 int cnt1=0; 12 int cnt2=0; 13 int n,k; 14 15 struct cmp1 16 { 17 bool operator()(const int a1,const int a2) 18 { 19 return a[a1]>a[a2]; //这里太精髓,重定义的是数组元素值,但是队列存的是下标 20 } 21 }; 22 struct cmp2 23 { 24 bool operator()(const int a1,const int a2) 25 { 26 return a[a1]<a[a2]; 27 } 28 }; 29 priority_queue <int ,vector<int>,cmp1> Q1; //重定义符号 30 priority_queue <int ,vector<int>,cmp2> Q2; 31 int main() 32 { 33 int i; 34 scanf("%d%d",&n,&k); 35 if(k>n) 36 k=n; 37 for(i=1;i<=n;++i) 38 { 39 scanf("%d",&a[i]); 40 } 41 for(i=1;i<=k;++i) 42 { 43 Q1.push(i); 44 Q2.push(i); 45 } 46 OutMin[cnt1++]=a[Q1.top()]; 47 OutMax[cnt2++]=a[Q2.top()]; 48 for(i=k+1;i<=n;++i) 49 { 50 Q1.push(i); 51 Q2.push(i); 52 while(i-Q1.top()>=k) 53 Q1.pop(); 54 OutMin[cnt1++]=a[Q1.top()]; 55 while(i-Q2.top()>=k) 56 Q2.pop(); 57 OutMax[cnt2++]=a[Q2.top()]; 58 } 59 60 for(i=0;i<=(n-k);++i) 61 { 62 printf("%d%c", OutMin[i], (i < n - k) ? ' ' : '\n'); 63 64 } 65 for(i=0;i<=(n-k);++i) 66 { 67 printf("%d%c", OutMax[i], (i < n - k) ? ' ' : '\n'); 68 } 69 return 0; 70 }