定义
单调队列就是具有单调性质的队列,即单调递减或单调递增的队列。
用途
在长度为n的数组中,从1到n-m+1分别为起点的连续的m个区间长度中求出最大值或最小值
处理过程
进队操作:将进队的元素为e,从队尾往前扫描,直到找到一个不大于e的元素d,将e放在d之后,舍弃e之后的所有元素;如果没有找到这样一个d,则将e放在队头(此时队列里只有这一个元素)。(这是维护单调递增性,如果要维护单调递减性,就找大于e的元素d,将e放在d之后,舍弃e之后的所有元素)
出队操作:取队首元素出队
基本思想
维护队首元素作为答案,去掉多余的元素(维护单调性)。
时间复杂度
每个元素最多进队一次,出队一次,摊排分析下来仍然是 O(1),总复杂度为复杂度O(n)。
例题
http://poj.org/problem?id=2823(单调队列模板)
题意:输入n和m,n代表数组的长度,m代表连续区间的长度,找出每个连续m个区间长度中的最大值和最小值。接下来输入n个数。
思路:这个问题相当于一个数据流(数列a)在不断地到来,而数据是不断过期的,相当于我们只能保存有限的数据(sliding window中的数据,此题中就是窗口的宽度w),对于到来的查询(此题中查询是每时刻都有的),我们要返回当前滑动窗口中的最大值\最小值。注意,元素是不断过期的。
解决这个问题可以使用一种叫做单调队列的数据结构,它维护这样一种队列:
a)从队头到队尾,元素在我们所关注的指标下是递减的(严格递减,而不是非递增),比如查询如果每次问的是窗口内的最小值,那么队列中元素从左至右就应该递增,如果每次问的是窗口内的最大值,则应该递减,依此类推。这是为了保证每次查询只需要取队头元素。
b)从队头到队尾,元素对应的时刻(此题中是该元素在数列a中的下标)是递增的,但不要求连续,这是为了保证最左面的元素总是最先过期,且每当有新元素来临的时候一定是插入队尾。
满足以上两点的队列就是单调队列,首先,只有第一个元素的序列一定是单调队列。
那么怎么维护这个单调队列呢?无非是处理插入和查询两个操作。
对于插入,由于性质b,因此来的新元素插入到队列的最后就能维持b)继续成立。但是为了维护a)的成立,即元素在我们关注的指标下递减,从队尾插入新元素的时候可能要删除队尾的一些元素,具体说来就是,找到第一个大于(在所关注指标下)新元素的元素,删除其后所有元素,并将新元素插于其后。因为所有被删除的元素都比新元素要小,而且比新元素要旧,因此在以后的任何查询中都不可能成为答案,所以可以放心删除。
对于查询,由于性质b,因此所有该时刻过期的元素一定都集中在队头,因此利用查询的时机删除队头所有过期的元素,在不含过期元素后,队头得元素就是查询的答案(性质a),将其返回即可。
代码:
#include <iostream>
#include<cstdio>
using namespace std;
const int Max = 1000004;
int n,m,a[Max],v[Max],mn[Max],mx[Max],head,tail;//v代表队列,存放的是数组a的下标
void getmin()
{
head = 1;tail = 0;//代表队列的头和尾
for(int i = 1; i < m; i++)//先把队列中初始的值确定
{
while(head <= tail && a[v[tail]] >= a[i]) tail--;
v[++tail] = i;
}
for(int i = m; i <= n; i++)
{
while(head <= tail && a[v[tail]] >= a[i]) tail--;//如果要插入的数比队尾的数还要大的话,就说明对答案没有影响,就可以删除此元素
v[++tail] = i;
while(v[head] < i-m+1) head++;
//mn[i-m+1] = a[v[head]];
printf("%d ",a[v[head]]);//输出队首元素,即最小值
}
cout << endl;
}
void getmax()//同理可得
{
head = 1;tail = 0;
for(int i = 1; i < m; i++)
{
while(head <= tail && a[v[tail]] <= a[i]) tail--;
v[++tail] = i;
}
for(int i = m; i <= n; i++)
{
while(head <= tail && a[v[tail]] <= a[i]) tail--;
v[++tail] = i;
while(v[head] < i-m+1) head++;
//mx[i-m+1] = a[v[head]];
printf("%d ",a[v[head]]);
}
cout << endl;
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++)
cin >> a[i];
getmin();
getmax();
}