滑动窗口(单调队列)

先从题目引入:

滑动窗口 /【模板】单调队列 - 洛谷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;
}

当然,输出最大值和输出最小值可以类比学习,保证队列中值递减,这样每次从队头出的值都是最大的.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值