滑动窗口(单调队列)

先简单描述一下单调队列:
单调队列和单调栈类似,就是队列内的元素是单调的,并且是满足出队顺序的单调性。它可以维护局部的单调性。由于单调队列可以队首出队以及前面的元素一定比后面的元素先入队的性质,使得它可以维护局部的单调性,并且当队首元素不在区间之内则可以出队,其复杂度也是线性的。

问题描述:

有一个长度为 n n n 的数列和一个大小为 k k k 的窗口, 窗口可以在数列上来回移动. 现在我们想知道在窗口从左往右滑的时候,每次窗口内数的最大值和最小值分别是多少. 例如:
滑动窗口(单调队列)_第1张图片

input:
输入有两行。第一行两个整数 n n n k k k分别表示数列的长度和滑动窗口的大小, 1 ≤ k ≤ n ≤ 1000000 1\leq k\leq n\leq1000000 1kn1000000。第二行有 n n n个整数表示数列。
output:
输出有两行。第一行输出滑动窗口在从左到右的每个位置时,滑动窗口中的最小值。第二行是最大值。

样例输入:

8 3
1 3 -1 -3 5 3 6 7

样例输出:

-1 -3 -3 -3 3 3
3 3 5 5 6 7

解题思路:

由于是求解区间内的最大最小值,是一个局部的概念,那么我们就是使用单调队列。我们可以维护一个单调递增队列和一个单调递减队列, 队列中的元素均属于当前窗口,当元素不属于当前窗口时, 将队首元素弹出即可。

我们可以通过两个下标,来模拟队列的队首和队尾指针,对于一个元素,其有两个域,一个是数值域,一个是下标域。由于单调队列复杂度为线性,那么我们可以使用一次for循环来实现对区间中最小值的查找,并且使用单调递增队列,这样我们就能保证队首为窗口最小值。查找最大值则相反。然后再用一次for循环实现对区间中最大值的差找。对于要压入队列的元素,我们首先要判断它是不是小于队尾元素,如果小于,将队尾元素弹出栈,直到满足条件为止。其次,由于每次只压入一个元素,我们可以通过一个if语句,判断当前元素的下标和队首元素的下标差是否大于窗口大小,若大于,将队首弹出栈。这样,我们当前压入队列所在的窗口中的最小值就是队首元素。这样我们就能找到所有元素所在窗口的最小元素,求最大值则将数组逆序利用单调递减队列即可。

代码:

#include
#include
#include
#include
using namespace std;
struct num{
	int date;  //元素值
	int location; //下标
	num & operator = (const num &n)
	{
		date=n.date;
		location=n.location;
		return *this;
	}
};
int tleft[1000010];//窗口最小值
int tright[1000010]; //窗口最大值
num tqueue[1000010];
num tqueue2[1000010];
num a[1000010];
int n,k;
int main()
{
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	{
		int temp;
		scanf("%d",&temp);
		a[i].date=temp;
		a[i].location=i;
	}
	int top=1,tail=0;
	for(int i=1;i<=n;i++)
	{
		//若队尾元素大于新元素,则弹出
		while(top<=tail&&tqueue[tail].date>a[i].date) tail--;
		tqueue[++tail]=a[i]; 
		//如果大于窗口长度 ,队首弹出
		if((tqueue[tail].location-tqueue[top].location+1)>k) top++;
		int hh=tqueue[tail].location;
		tleft[hh]=tqueue[top].date;//当前元素所在窗口的最小值
	}
	top=1,tail=0;
	for(int i=n;i>=1;i--)//最大值同理
	{
		while(top<=tail&&tqueue2[tail].date<a[i].date) tail--;
		tqueue2[++tail]=a[i];
		if((tqueue2[top].location-tqueue2[tail].location+1)>k) top++;
		int hh=tqueue2[tail].location;
		tright[hh]=tqueue2[top].date;
	}
	int window=n-k+1;
	for(int i=k;i<=n;i++)
		cout<<tleft[i]<<" ";
	cout<<endl; 
	for(int i=1;i<=n-k+1;i++)
		cout<<tright[i]<<" ";
}

你可能感兴趣的:(滑动窗口(单调队列))