单调队列是一种队列(废话)
其中队列的元素保证是单调递增或者是单调递减的
那么队首的元素不就是最小(或最大)的吗?
单调队列两端弹出元素,队尾插入元素
我们结合具体的题目来看看吧:
解法1:O(n^2)
如果按照常规方法,我们在求a[i] 即i~i+k-1区间内的最值
时,要把区间内的所有数都访问一遍,时间复杂度约为
O(nk)O(nk)O(nk)。有没有一个快一点的算法呢?
解法2:O(n):每个元素最多入队出队一次
很明显,当我们在计算区间 [i−k+1,i][i-k+1,i][i−k+1,i]的最大值时,是不
是区间 [i−k+1,i−1][i-k+1,i-1][i−k+1,i−1]我们之前已经计算过了?那么我们
是不是要保存上一次的结果呢(主要是最大值)?
这时,单调队列登场——
单调队列主要有两个操作:删头和去尾
1.删头
如果队列头的元素离开了我们当前操作的区间,那么这
个元素就没有任何用了,我们就要把它删掉
2.去尾
假设即将进入队列的元素为 XXX,队列尾指针为 tailtailtail,
这时我们要比较二者的大小:
1* X<q[tail]X<q[tail]X<q[tail]
此时q仍然保证着递减性,故直接将 XXX插入队列尾
2* X>=q[tail]X>=q[tail]X>=q[tail]
此时,队列递减性被打破,此时我们做一下操作:
-
弹出队尾元素
因为当前的 q[tail]q[tail]q[tail]不但不是最大值,对于以后的情况
也不如 XXX更优,所以要弹出
这好比当前队列尾部的元素是个能力不足的老兵,而新加入
的新兵更年轻,更能打,当然不要老兵了 -
重复执行①,直到满足 X<q[tail]X<q[tail]X<q[tail]或者队列为空为止
-
将 XXX插入队列
对于样例而言:
[1 3 -1] -3 5 3 6 7
q={1},{3},{3,-1} output:3//分别为每次操作的结果
1 [3 -1 -3] 5 3 6 7
q={3,-1,-3} output:3
1 3 [-1 -3 5] 3 6 7
q={-1,-3},{-1,5},{5} output:5
1 3 -1 [-3 5 3] 6 7
q={5,3} output:5
1 3 -1 -3 [5 3 6] 7
q={5,6},{6} output:6
1 3 -1 -3 5 [3 6 7]
q={6} output:7
#include<iostream>
using namespace std;
const int N=1000005;
int n,k;
//a[N]存储输入的数组,q[N]存贮符合要求的数组的下标
int a[N],q[N];
int hh=0,tt=-1;
int main(){
cin>>n>>k;
for(int i=0;i<n;i++)
{
cin>>a[i];
}
//求滑动窗口中最小值
for(int i=0;i<n;i++){
//判断队头是否还在滑窗之内
if(hh<=tt && i-k+1>q[hh]){
hh++;
}
while(hh<=tt && a[q[tt]]>=a[i]){//如果队列中的值比当前的值大,那么从队尾弹出(保证是递增的)
tt--;
}
q[++tt]=i;//把当前数的下标存入队列中
if(i>=k-1){
cout<<a[q[hh]]<<" ";
}
}
cout<<endl;
//求当前窗口中的最大值
hh=0;//记得初始化...
tt=-1;
for(int i=0;i<n;i++){
//判断队头是否还在滑窗之内
if(hh<=tt && i-k+1>q[hh]){
hh++;
}
while(hh<=tt && a[q[tt]]<=a[i]){//和求最小值对称就行
tt--;
}
q[++tt]=i;;
if(i>=k-1){
cout<<a[q[hh]]<<" ";
}
}
cout<<endl;
}
模板
常见模型:找出滑动窗口中的最大值/最小值
int hh = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{
while (hh <= tt && check_out(q[hh])) hh ++ ; // 判断队头是否滑出窗口
while (hh <= tt && check(q[tt], i)) tt -- ;
q[ ++ tt] = i;
}