题意
ZJM 有一个长度为 n 的数列和一个大小为 k 的窗口, 窗口可以在数列上来回移动. 现在 ZJM 想知道在窗口从左往右滑的时候,每次窗口内数的最大值和最小值分别是多少.
例如:数列是 [1 3 -1 -3 5 3 6 7], 其中 k 等于 3.
Input
输入有两行。第一行两个整数n和k分别表示数列的长度和滑动窗口的大小,1<=k<=n<=1000000。第二行有n个整数表示ZJM的数列。
Output
输出有两行。第一行输出滑动窗口在从左到右的每个位置时,滑动窗口中的最小值。第二行是最大值。
Example
Input
8 3
1 3 -1 -3 5 3 6 7
Output
-1 -3 -3 -3 3 3
3 3 5 5 6 7
单调队列总结
单调队列 = 单调 + 队列 (队列内元素满足出队顺序的单调性)
模拟单调队列操作(以单调递增队列为例)
现有一数组 [10, 3, 7, 4, 12],从左到右依次入队。
• 如果队为空或者队尾元素小于入队元素,则入队。
• 否则,入队则会破坏队内元素的单调性,则需要将不满足条件的队尾元素全部出队后,将入队元素入队。
具体入队过程如下:(“【】”表示队列)
①10入队时 【】 队空 10入队
②3入队时 【10】 队尾元素10 10>3 10出队;队空,3入队
③7入队时 【3】 队尾元素3 3<7 7入栈
④4入队时 【3 7】 队尾元素7 7>4 7出队;3<4,4入队
⑤12入队时 【3 4】 队尾元素4 12>4 12入队
⑥最后的队 【3 4 12】
单调递增队列的伪码
单调队列与单调栈
- 单调队列的维护过程与单调栈相似
- 区别在于
①单调栈只维护一端(栈顶),而单调队列可以维护两端(队首和队尾)
②单调栈通常维护全局的单调性,而单调队列通常维护局部的单调性
③单调栈大小没有上限,而单调队列通常有大小限制 - 由于单调队列可以队首出队以及前面的元素一定比后面的元素先入队的性质,使得它可以维护局部的单调性
- 当队首元素不在区间之内则可以出队
- 时间复杂度与单调栈一致
思想
本题要求查找窗口内的最小值,是一个局部的概念,所以可以使用单调队列
- 维护一个单调递增队列,队列中的元素均属于当前窗口
- 当元素不属于当前窗口时,将对手元素弹出即可
例题模拟
代码
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <algorithm>
using namespace std;
int a[1100000],q[1100000];
int MIN[1100000],MAX[1100000];
int l,r;
int main()
{
int n,k;
scanf("%d%d",&n,&k);
memset(a,0,sizeof(a));
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
l=1,r=0;//l<r 队列为空
//q[i]-队列中i元素的下标
//q[r]-队尾元素的下标
for(int i=1;i<=n;i++)
{//求最小值
while(r>=l&&a[i]<=a[q[r]]) r--; //入队元素小于队尾,弹出队尾
q[++r]=i; //把a[i]的下标加入队列
if(q[r]-q[l]+1>k) l++; //维护队列大小恒小于k
if(i>k-1) MIN[i-k+1]=a[q[l]];
}
l=1,r=0;//l<r 队列为空
//q[i]-队列中i元素的下标
//q[r]-队尾元素的下标
for(int i=1;i<=n;i++)
{//求最大值
while(r>=l&&a[i]>=a[q[r]]) r--; //入队元素小于队尾,弹出队尾
q[++r]=i; //把a[i]的下标加入队列
if(q[r]-q[l]+1>k) l++; //维护队列大小恒小于k
if(i>k-1) MAX[i-k+1]=a[q[l]];
}
for(int i=1;i<=n-k+1;i++)
printf("%d ",MIN[i]);
printf("\n");
for(int i=1;i<=n-k+1;i++)
printf("%d ",MAX[i]);
return 0;
}