单调队列,顾名思义,就是(严格)单调(递增或递减)的队列。下面以单调递减队列为例。
单调递减队列:
1.单调递减的队列,队首元素总是最大的。
2.元素只能从队尾入队,但可以从队尾或队首出队。若待入队的元素>=队尾元素,队尾元素出队,直到待入队的元素<队尾元素,或队列为空,然后待入队的元素从队尾入队;若新入队的元素与队首元素的距离>=窗口宽度,说明队首元素已不在窗内,队首元素出队。
3.单调队列有两个单调性:(1)元素的值是严格单调的,这里是严格单调递减;(2)元素的下标总是严格单调递增的。
举个例子,有8个元素1, 3, -1, -3, 5, 3, 6, 7,窗口的宽度是3,沿着排列滑动,输出每个窗口的最大值。
例如,1, [3, -1, -3,] 5, 3, 6, 7,窗口中的三个元素是3, -1, -3,最大值是3。
于是,输出的值依次是3, 3, 5, 5, 6, 7。
题目是给定n个元素,窗口宽度是m,随着窗口的滑动,依次输出窗中元素的最大值和最小值。
我的算法是,首先对前m个元素由小到大排序,于是最小值是排序后数列的头元素,最大值是尾元素。移动窗口至下一格,排序好的数列插入一个元素,删除一个元素,仍然保持单调性,于是最小值仍然是头元素,最大值仍然是尾元素。搜索的时间复杂度是lgm(二分查找),插入/删除的时间复杂度是1(memcpy),进行n-m+1次,于是算法的时间复杂度是O(nlgm)。
下面用单调递减队列解决,仍然以1, 3, -1, -3, 5, 3, 6, 7为例。
队列为空,1直接入队。1
下一个元素3,大于1,1出队,3入队。3
下一个元素-1,小于3,-1入队。3, -1
下一个元素-3,小于-1,-3入队。3, -1, -3
下一个元素5,依次与队尾元素比较直到队尾元素>5或队空。于是-3, -1, 3依次出队,5入队。5
下一个元素3,小于5,入队。5, 3
下一个元素6,3, 5依次出队,6入队。6
下一个元素7,6出队,7入队。7
考察单调队列队首元素,可得3, 3, 5, 5, 6, 7。
下面是单调递减队列的代码。
int inputque[MAXN] //输入序列
int indexque[MAXN] //单调队列,事实上保存的是元素下标
int maxout[MAXN] //输出窗内元素的最大值
int rear=-1, front=0; //初始化队列为空。当rear+1 == front,队列为空;若rear==front,队列中只有一个元素。
for (int i=0; i<MAXN; ++i) //遍历输入队列,i为当前下标
{
if (rear+1 == front) indexque[++rear] = i; //若队列为空,直接入队
else
{
while (inputque[i] >= inputque[indexque[rear]] && rear>=front) --rear; //若队尾元素<=当前元素且队列不为空,队尾元素出队直到...
indexque[++rear] = i; //当前元素入队
if (i - indexque[front] == m) ++front; //若队首元素已不在窗内,即当前元素的下标与队首元素的下标之差超过m,队首元素出队
}
maxout[i] = inputque[indexque[front]]; //输出单调队列队首元素,即最大元素。
}
初始输入序列的大致每个元素都入队一次、出队一次,这两个操作都消耗常数时间,所以时间复杂度为O(n),即线性时间复杂度。