单调队列:即单调递减或单调递增的队列,通常处理滑动窗口类的问题
题意大致:有一个大小为 m 的滑动窗口,每次要输出窗口中的最大值
eg:有数组[3 5 2 6 3 9],m为3
单调队列的思想过程:
滑动窗口 最大值 ·状态
【3】 5 2 6 3 9 暂无 滑动窗口内无数据,从队尾排入一数【3】
【3 5 】2 6 3 9 暂无 从队尾排入第二个数,5>3,则3从队头出列【5】
【3 5 2 】6 3 9 5 从对尾排入第三个数,窗口内已有过三个数【5 2】
3 【5 2 6】 3 9 6 移动窗口,从队尾排入6,6>2,排出2,6>5,排出5【6】
3 5 【2 6 3】 9 6 移动窗口,从队尾排入3【6 3】
3 5 2【 6 3 9】 9 移动窗口,从队尾排入9,9>3,9>6均排出【9】
看到这里,你可能会想到这样写代码:
for(int i=m;i<n;i++)
{
int max1=a[i];
for(int j=i-m+1;j<=i;j++)
max1=max(max1,a[j]);
cout<<max1<<endl;
}
这样写没有问题,但是时间复杂度无法想象,仔细想一想,有没有发现i-1个元素都是已经比较过了的,再次比较每次都是重复比较的,那为什么不保存上次比较的最大值呢,这就是单调队列的思想,那现在看看神奇的单调队列是怎么完成的吧:
1、队头出队:滑动窗口移动时,队尾移入一个,队头相应移除一个(h++)
2、队尾入队:新元素滑入窗口时,从尾端插入分两种情况:
(1)直接插入:如果新元素小于队尾元素,直接从队尾插入(++t)
(2)先删后插:如果新元素大于队尾的元素,就要先删除队尾元素(t--)(这里用到循环删除,直到队空或者队尾元素大于新元素)
代码展示:
#include <iostream>
using namespace std;
int main()
{
int h=0;//头部下标记号
int t=-1;//尾部下表记号
int n,m;//数组个数&要限定的范围
int a[100];//输入数组
int q[100];//存放下标
cin>>n>>m;
for(int i=1;i<=n;i++)//遍历整个数组
{
cin>>a[i];
if(h<=t&&q[h]<i-m+1)//当头部小于等于尾部时,当前下标不在[i-m+1,i]的范围内时,队头出队
h++;
while(h<=t&&a[i]>a[q[t]])//当头部小于等于尾部时,当前值>队尾值,队尾出队(做一个循环比较,直到队尾值不再比当前值大为止)
t--;
q[++t]=i;//注意前面t多--了,所以这里要++t,不能t++
if(i>=m)//输出[m,i]的范围内每增一个元素内的最大值,即队头
cout<<a[q[h]]<<endl;
}
}
注意:队列用存储元素下标值的方法是便于判断队头出列