先从题目引入:
滑动窗口 /【模板】单调队列 - 洛谷https://www.luogu.com.cn/problem/P1886现在题目要求在长度为n的数组中存在一个长度为k的窗口,依次输出窗口内的最大最小值.
此题是单调队列的模板题,首先对单调队列有一个抽象的理解,
如图:
单调队列,即为"队列"中我们从队尾依次进入的值是单调(递增或者递减)的,这样可以保证从队头出去的代码永远是队伍中最小的.
该队列应该是一个双端队列(我们这里用数组去模拟,也可以尝试双端队列去实现)首先就要实现的是把数组中的数字装入队列中,从队尾入队,队首出队.然后就要去理解如何保证队列里面的数字是单调的.假设要取每次队列中的最小值,那么我们就保证优先队列中按次序存入的数据是保证递增的,这样的话每次在队头都是在这个单调序列中的最小值,如上图,每次都是从右边进,从左边出.
为了保证队列的单调性,我们要如此考虑:
(1)遍历数组,当数组的数比队尾的数要大时,我们可以直接入队尾,因为它入队后还是可以保证队列的单调性.
(2)当遍历到的数组的数比队尾要小,此时破坏了队列中数字的单调性,那么我们就需要把前一个队尾的数字进行出队.然后在进行判断,如果是(1)情况就入队尾,如果还是(2)情况,那么我们就继续出队在进行比较队尾和遍历到的数,这里用while函数写,直到比较到遍历到的数入队时还能使队伍保持单调.
这就是滑动窗口,比较好理解,多在纸上模拟几次就行了,代码如下:
#include<iostream>
using namespace std;
int n,k,hh=0,tt=0;
int arr[1000006],q[1000006];
//arr存储的是数据,q存储的是小标
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%d",&arr[i]);
if(q[hh]<i-k+1)++hh;
//当队头超出窗口范围,把它从队列中去除
while(hh<=tt&&arr[q[tt]]>=arr[i])tt--;
//hh<=tt判断队伍是否为空,当arr[q[tt]]>=arr[i]表示队尾的元素比遍历的数组的值大,
//要保证递增,所以要把队尾的元素去除
q[++tt]=i;
//入队
if(i>=k)printf("%d ",arr[q[hh]]);
如果窗口的值满了,就把最小输出
}
printf("\n");
hh=0,tt=-1;
for(int i=1;i<=n;i++)
{
if(i-k+1>q[hh])++hh;
while(tt>=hh&&arr[q[tt]]<=arr[i])tt--;
q[++tt]=i;
if(i>=k)printf("%d ",arr[q[hh]]);
}
return 0;
}
当然,输出最大值和输出最小值可以类比学习,保证队列中值递减,这样每次从队头出的值都是最大的.