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.
8 3 1 3 -1 -3 5 3 6 7
-1 -3 -3 -3 3 33 3 5 5 6 7
题目大意:给你一个数组,然后让从左到右,找到每连续k个数的最大值和最小值并且输出
题目分析:单调队列解决,然后用C++卡时间AC,用G++会超时
这个问题相当于一个数据流(数列a)在不断地到来,而数据是不断过期的,相当于我们只能保存有限的数据(sliding window中的数据,此题中就是窗口的宽度w),对于到来的查询(此题中查询是每时刻都有的),我们要返回当前滑动窗口中的最大值\最小值。注意,元素是不断过期的。
解决这个问题可以使用一种叫做单调队列的数据结构,它维护这样一种队列:
a)从队头到队尾,元素在我们所关注的指标下是递减的(严格递减,而不是非递增),比如查询如果每次问的是窗口内的最小值,那么队列中元素从左至右就应该递增,如果每次问的是窗口内的最大值,则应该递减,依此类推。这是为了保证每次查询只需要取队头元素。
b)从队头到队尾,元素对应的时刻(此题中是该元素在数列a中的下标)是递增的,但不要求连续,这是为了保证最左面的元素总是最先过期,且每当有新元素来临的时候一定是插入队尾。
满足以上两点的队列就是单调队列,首先,只有第一个元素的序列一定是单调队列。
那么怎么维护这个单调队列呢?无非是处理插入和查询两个操作。
对于插入,由于性质b,因此来的新元素插入到队列的最后就能维持b)继续成立。但是为了维护a)的成立,即元素在我们关注的指标下递减,从队尾插入新元素的时候可能要删除队尾的一些元素,具体说来就是,找到第一个大于(在所关注指标下)新元素的元素,删除其后所有元素,并将新元素插于其后。因为所有被删除的元素都比新元素要小,而且比新元素要旧,因此在以后的任何查询中都不可能成为答案,所以可以放心删除。
对于查询,由于性质b,因此所有该时刻过期的元素一定都集中在队头,因此利用查询的时机删除队头所有过期的元素,在不含过期元素后,队头得元素就是查询的答案(性质a),将其返回即可。
由于每个元素都进队出队一次,因此摊销复杂度为O(n)。
程序实现过程中先将前k个元素入队,此后每次在队尾加入a[k+1...n],在插入元素中同时进行以下操作:
1、将队尾所有大于a[i]的值弹出队列
2、插入a[i]到队尾
3、判断队首元素位置是否超出i-k
注意:
1、在更新队列元素时要同时记录该元素在原数据的位置
2、在进行操作1时要用二分优化(可以用C++编译器卡时间AC,但换成G++和GCC就会TLE)
#include <iostream> #include <fstream> #define maxn 1000010 using namespace std; int a[maxn],q[maxn],p[maxn],Min[maxn],Max[maxn],n,k; // a是数据,q是队列,p是存放下标 void gmin(){ int tail=0,head=1,i; for(i=0;i<k-1;i++){ while(head<=tail && q[tail]>=a[i])//若添加的元素小于队尾元素,符合条件,删除队尾元素,直到队尾元素小于此元素为止 --tail; q[++tail]=a[i];//存放满足条件的数据 p[tail]=i;//记录存储的数据下标,保证每个窗口所读入的数据都在k的范围内 } for(;i<n;i++){ while(head<=tail && q[tail]>=a[i]) --tail; q[++tail]=a[i]; p[tail]=i; while(p[head]<i-k+1)//利用查看数据是否过时,即是否在窗口内 ++head; Min[i-k+1]=q[head];//存放数据答案 } } void gmax(){ int tail=0,head=1,i; for(i=0;i<k-1;i++){ while(head<=tail && q[tail]<=a[i]) --tail; q[++tail]=a[i]; p[tail]=i; } for(;i<n;i++){ while(head<=tail && q[tail]<=a[i]) --tail; q[++tail]=a[i]; p[tail]=i; while(p[head]<i-k+1) head++; Max[i-k+1]=q[head]; } } void output(){ for(int i=0;i<n-k;i++) printf("%d ",Min[i]); printf("%d\n",Min[n-k]); for(int i=0;i<n-k;i++) printf("%d ",Max[i]); printf("%d\n",Max[n-k]); } int main(){ scanf("%d%d",&n,&k); for(int i=0;i<n;i++) scanf("%d",&a[i]); gmin(); gmax(); output(); return 0; }