单调队列是一种用来快速查询一段长度为n的数组的一部分的最大最小值的算法。它原理相对简单,且易于实现,并且时间复杂度是接近O(n)的,可以解决很多问题。下面以洛谷P1886滑动窗口 /【模板】单调队列为例来介绍单调队列的实现方法。
先放上题目:
题目描述
有一个长为n
例如:
The array is
输入格式
输入一共有两行,第一行有两个正整数
输出格式
输出共两行,第一行为每次窗口滑动的最小值
第二行为每次窗口滑动的最大值
解法介绍:
单调队列并不使用queue,而只需要在原序列中维护两个边界,分别为l和r,代表研究的部分的左边界位置、右边界位置。我们将左边界的位置初始化为1,右边界的位置初始化为k,代表最初这个研究部分只包含a[1]到a[k]这些节点。此时,我们并不知道这个子区间的最大值和最小值分别是多少,所以我们需要维护一个find函数来寻找子区间内的最大、最小值。这里我们进行一次find初始化,并且对最初的最大值、最小值进行记录。find函数只需要从l到r进行枚举查询即可。
find函数代码实现如下:
1 const int inf=0x3f3f3f3f;
2 void find(int l,int r){
3 maxn=-inf;minn=inf;//初始化最大值和最小值
4 for(int i=l;i<=r;i++){
5 maxn=max(maxn,a[i]);
6 minn=min(minn,a[i]);
7 }
8 }
接下来我们需要对这个窗口进行滑动操作。我们总共需要向右移动(n-k+1)次,每一次都要进行l++,r++的操作。而在操作的时候我们需要维护最值,这也是最为重要的操作。
我们在维护时要分很多种情况:
第一种,也是最好处理的一种情况,当在进行l++操作之前的l所对应的a[l]既不是上一个窗口所对应的最大值,又不是它所对应的最小值时,删去这个值并不会影响窗口内的最大值和最小值,这时候我们可以放心的l++,r++,然后用新加入单调队列的a[r]对maxn和minn进行update更新即可。
代码:
1 void update(){
2 l++;
3 r++;
4 maxn=max(maxn,a[r]);
5 minn=min(minn,a[r]);
6 }
7 //接下来是在循环中的判断
8 if(a[l]!=maxn&&a[l]!=minn){
9 update();
10 }
第二种,当在进行l++操作之前的l所对应的a[l]是上一个窗口所对应的最大值,而不是它所对应的最小值时,删除a[l]会对新区间内的最大值造成影响,我们已经不知道除了这个被删除掉的节点以外,新窗口的最大值是多少,所以我们要重新查找。但是,这种情况不会对最小值造成影响。但是,如果新加入的节点要大于等于要删除的节点,那么删除掉这个节点也就无所谓了。所以我们还需要加入一个判断:
1 if(a[l]==maxn){//如果左边界的值等于最大值
2 if(a[r+1]>=a[l]){//新加入节点值大于等于删除的节点
3 update();//不需要重新查找
4 }else{
5 l++;
6 r++;
7 find(l,r);//否则重新查找
8 }
9 }
第三种,当在进行l++操作之前的l所对应的a[l]是上一个窗口所对应的最小值,而不是它所对应的最大值时,我们可以用与第二种情况相同的方式来查找。代码如下:
1 if(a[l]==minn){
2 if(a[r+1]<a[l]){
3 update();
4 }else{
5 l++;
6 r++;
7 find(l,r);
8 }
9 }
第四种,也是最为玄学的一种情况,就是当在进行l++操作之前的l所对应的a[l]既是上一个窗口所对应的最小值,又是它所对应的最大值的情况。这种情况下直接重新查找就可以了,毕竟这种情况极少,且考虑起来会很麻烦。
代码:
1 if(a[l]==maxn&&a[l]==minn){
2 l++;
3 r++;
4 find(l,r);
5 }
最后附上完整代码:
1 #include<iostream>
2 #include<algorithm>
3 using namespace std;
4 const int inf=0x3f3f3f3f;
5 int n,k;
6 int l,r;
7 int a[1000005];
8 int maxn,minn;
9 int max_ans[1000005],min_ans[1000005];
10 void record(int now){
11 max_ans[now]=maxn;
12 min_ans[now]=minn;
13 }
14 void find(int l,int r){
15 maxn=-inf;
16 minn=inf;
17 for(int i=l;i<=r;i++){
18 maxn=max(maxn,a[i]);
19 minn=min(minn,a[i]);
20 }
21 }
22 void update(){
23 l++;
24 r++;
25 maxn=max(a[r],maxn);
26 minn=min(a[r],minn);
27 }
28 int main(){
29 cin>>n>>k;
30 for(int i=1;i<=n;i++){
31 cin>>a[i];
32 }
33 l=1;
34 r=k;
35 find(l,r);
36 for(int i=1;i<=n-k+1;i++){
37 record(i);
38 if(a[l]!=maxn&&a[l]!=minn){
39 update();
40 }else if(a[l]==maxn&&a[l]==minn){
41 l++;
42 r++;
43 find(l,r);
44 }else if(a[l]==maxn){
45 if(a[r+1]>a[l]){
46 update();
47 }else{
48 l++;
49 r++;
50 find(l,r);
51 }
52 }else{
53 if(a[r+1]<a[l]){
54 update();
55 }else{
56 l++;
57 r++;
58 find(l,r);
59 }
60 }
61 }
62 for(int i=1;i<=n-k+1;i++){
63 cout<<min_ans[i]<<' ';
64 }
65 cout<<endl;
66 for(int i=1;i<=n-k+1;i++){
67 cout<<max_ans[i]<<' ';
68 }
69 cout<<endl;
70 return 0;
71 }