单调队列的原理及其实现

单调队列是一种用来快速查询一段长度为n的数组的一部分的最大最小值的算法。它原理相对简单,且易于实现,并且时间复杂度是接近O(n)的,可以解决很多问题。下面以洛谷P1886滑动窗口 /【模板】单调队列为例来介绍单调队列的实现方法。

先放上题目:

题目描述

有一个长为n的序列a,以及一个大小为k的窗口。现在这个窗口从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。

例如:

The array is [1,3,-1,-3,5,3,6,7],and k=3。

输入格式

输入一共有两行,第一行有两个正整数n,k。第二行n个整数,表示序列a

输出格式

输出共两行,第一行为每次窗口滑动的最小值
第二行为每次窗口滑动的最大值

解法介绍:

单调队列并不使用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 }
原文地址:https://www.cnblogs.com/qianr/p/13821867.html