传送门
方法:单调队列
所用到的:
// hh 表示队首,tt表示队尾
int hh=0,tt=-1;
//用数组来模拟队列
int q[N],a[i];
思路:
其实简单来说,就是来维护队首元素。
就拿求最小值来时,当我们每一次将当前元素压入队列的时候,我们就要进行判断,如果队尾元素 > a[i] 的时候,我们就应该弹出当前队尾元素,对于数组而言 即 tt--
,因为我们要保证队首为当前窗口的最小值。
当然,每一次压入元素的时候,都要判断一下,队首即最小值是不是为当前窗口所属的区间。
拿一个例子来说一下:
1 3 -1 -3 4 5 6四个元素,k=3
- 首先将 1 的下标压入队列
- 到第二个元素 3 时,我们要依次判断,队列里的元素是不是比3小,如果碰到比 3 还大的元素那么此元素元素一定不是最小值,将指向队尾的元素
tt --
- 现在队列里的元素为 1 3
- 到第三个元素 -1 时,进行依次判断,发现1 3 都比 -1 要大,那说明1和3 一定不是答案,将1 3 弹出队列,-1 变成的队首元素,输出 -1。
- 到第四个元素 -3 时,和上面一样,发现 -1 比 -3 大,说明当前窗口区间的最小值一定不是 -1 ,将 -1 弹出 -3 压入,所以,当前区间最小值为 -3
……
当到第七个元素时,我们发现,-3 已经不在当前 4 5 6这个区间,所以我们要把 -3 弹出;
这里我们不难发现,我们一直维护的都是队首元素,当我们求最大值也是一样。
Ac code:
#include <iostream>
using namespace std;
const int N = 1e6+10;
int n,k;
int a[N],q[N];
int main()
{
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++)
scanf("%d",a+i);
//压进队列中的是元素的下标
int 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;
//只有当 i >= k - 1的时候才能输出
if(i>=k-1)
printf("%d ",a[q[hh]]);
}
puts("");
//求最大值
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)
printf("%d ",a[q[hh]]);
}
puts("");
return 0;
}