文章目录
单调队列结构解决滑动窗口问题
什么是单调队列?
单调队列其实就是一个队列,只是使用了一点巧妙的方法使得队列中的元素全都是单调递增(或单调递减)的
单调队列主要解决以下问题:
给你一个数组
window
,已知其最值为A
,如果给window
中添加一个数B
,那么比较一下A
和B
就可以立即算出新的最值;但如果要从window
数组中减少一个数,就不能直接得到最值了,因为如果减少的这个数恰好是A
,就需要遍历window
中的所有元素重新寻找新的最值。针对这个问题我们可以用优先级队列解决吗?
我们都知道优先级队列是一个特殊的队列,专门用来动态寻找最值得
但是如果只是淡出维护最值得话,优先级队列很专业,队头元素就是最值。
不过,优先级队列无法满足标准队列结构先进先出的时间顺序,因为优先级队列底层利用二叉堆进行动态排序,元素的出对顺序是元素的大小顺序和入队的先后顺序完全没有关系
因此我们用单调队列这种新的队列结构,既能维护队列元素先进先出的时间顺序又能够正确维护队列中所有元素的最值
总结:单调队列这个数据结构主要用来辅助解决滑动窗口相关的问题的。之前有关滑动窗口的问题我们使用的是双指针技巧但是有些稍微复杂的滑动窗口问题是不能只靠两个指针来解决的,需要更先进的数据结构
比如说每当窗口扩大和窗口缩小时,单凭移除和移入窗口的元素即可决定是否更新答案,但是当要从窗口中减少一个元素,我们无法单凭溢出的那个元素更新窗口的最值,除非重新遍历所有元素,但是这样就会增加时间复杂度
239. 滑动窗口最大值
对于这个问题我们可以利用单调队列结构用O(1)时间算出每个滑动窗口中的最大值使得整个算法在线性时间完成
单调队列框架
首先普通的队列的框架一般是这样的
class Queue {
// enqueue 操作,在队尾加入元素 n
void push(int n);
// dequeue 操作,删除队头元素
void pop();
}
而单调队列的框架也是差不多的
class Queue {
// enqueue 操作,在队尾加入元素 n
void push(int n);
// dequeue 操作,删除队头元素
void pop();
}
虽然是差不多但是实际的实现跟普通队列还是不一样的
观察滑动窗口的过程很容易发现,实现单调队列必须使用一种数据结构支持在头部和尾部进行插入和删除,而双链表满足这个条件
单调队列的核心思想和单调栈类似,push方法依然在队尾添加元素,但是要把前面比自己小的元素都删除掉
class MonotonicQueue {
// 双链表,支持头部和尾部增删元素
// 维护其中的元素自尾部到头部单调递增
private LinkedList<Integer> maxq = new LinkedList<>();
// 在尾部添加一个元素 n,维护 maxq 的单调性质
public void push(int n) {
// 将前面小于自己的元素都删除
while (!maxq.isEmpty() && maxq.getLast() < n) {
maxq.pollLast();
}
maxq.addLast(n);
}
我们可以这样理解:
加入数字的大小相当于人的体重,把前面体重不足的都压扁了,知道遇到更大的两集才停住
如果每个元素被加入时都是这样的操作,最终单调队列中的元素大小就会保持一个单调递减的顺序,所以max方法可以如下:
class MonotonicQueue {
// 为了节约篇幅,省略上文给出的代码部分...
public int max() {
// 队头的元素肯定是最大的
return maxq.getFirst();
}
}
pop方法在队头删除元素n也很好写,但是我们要判断 data.getFirst() == n
,这是因为我们想删除的队头元素n可能已经被压扁了,即可能不存在了,所以这时候就不用删除
class