滑动窗口(单调队列)

110 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第18天,点击查看活动详情

题目链接:154. 滑动窗口 - AcWing题库

这是一个单调队列模板题,常用于加速DP,因为经常会被用到,所以我今天来仔细讲讲这种题目的解决方法,当然说点题外话,这道题还可以用静态查询最大值和最小值的方法来解决,这个我就不详细介绍了,下面主要来用单调栈来介绍一下这道题的解法。

因为这道题要求最大值和最小值,由于这两种求法基本上是一模一样的,所以下面我主要介绍一下如何求连续k个元素的最大值:

队列要求我们必须要从对头出元素队尾进元素,由于题目上写着单调队列,所以队列中的元素肯定是单调的,但是需要注意的一点是我们队列中存储的是元素编号而不是真正的元素的值,所以我们每次进出队列都需要进行判断,假如我们当前遍历到了第i个元素,我们首先需要把不可能作为答案的元素都剔除队列,什么叫不可能作为答案的元素呢,首先我们知道已经遍历到了第i个元素,那么编号为第i-k个元素及之前的元素都不可能作为之后的答案了,因为所查询区间边界是递增的,所以我们可以把这些元素剔除掉,还有一部分元素需要剔除,就是在当前元素之前加入且小于当前元素的那些元素,为什么说这些元素不可能作为答案呢?首先一点是如果这些元素在查询区间内,由于我们的查询区间是递增的,所以我们当前加入的元素一定在查询区间内,再者是当前元素大于之前加入的元素,所以之前加入的元素但比当前元素小的元素肯定不会作为答案,所以就可以剔除。这就是单调队列的思想了,具体代码实现希望大家好好理解一下,主要解决的问题就是多次求解一个区间的满足某种性质的值,这样我们就不用一次一次查找了,常用于对dp的优化,我在之后会介绍到这样的问题。

下面是本道题目的代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
const int N=1e6+10;
int q[N],tt,hh,n,k,a[N];
int main()
{
	cin>>n>>k;
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)//越靠近队头的元素越小 
	{
		while(hh<tt&&a[q[tt]]>=a[i]) tt--;
		q[++tt]=i;
		while(hh<tt&&q[hh+1]<=i-k) hh++;
		if(i>=k) printf("%d ",a[q[hh+1]]);
	}
	puts("");
	hh=tt=0;
	for(int i=1;i<=n;i++)//越靠近队头的元素越大 
	{
		while(hh<tt&&a[q[tt]]<=a[i]) tt--;
		q[++tt]=i;
		while(hh<tt&&q[hh+1]<=i-k) hh++;
		if(i>=k) printf("%d ",a[q[hh+1]]);
	} 
	return 0;
}