单调队列与Sliding Window

所谓单调队列——即元素具有单调性且同时保持着队列性质的数据结构。这个数据结构使用频率不是很高,笔者在之前也没有接触过,但是很多时候可以简化问题,对一些算法优化也大有裨益。

背景

记录这篇文章的背景,要从周末时候刷到的leetcode 上239上的问题开始谈起了。题目的名称叫Sliding Window Maximum。以下是这个问题的描述。

Given an array nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position.

For example,

Given nums=[1,3,-1,-3,5,3,6,7], and k= 3.

Window position      Max

---------------              -----

[1  3  -1] -3  5  3  6  7      3

1 [3  -1  -3] 5  3  6  7      3

1  3 [-1  -3  5] 3  6  7      5

1  3  -1 [-3  5  3] 6  7      5

1  3  -1  -3 [5  3  6] 7      6

1  3  -1  -3  5 [3  6  7]      7

Therefore, return the max sliding window as[3,3,5,5,6,7].

我们分析下这个题目,滑动窗口每移动一次都要将原窗口中的元素弹出一个,压入一个,同时获取一个最大值。到这一步,我们能联想到什么样的数据结构呢?双向队列+优先级队列。如果我们使用一个优先级队列来做这个题目,毫无疑问是可以完成的。笔者一开始也是想使用优先级队列作为解题的方法。

但是一来对一个堆执行查找和随机删除并不是件很愉快的事情、二来每次执行heapify操作的时间复杂度为O(logk),总的时间代价需要O(nlogk),要知道,题目提示这个题目只需要线性时间内就可以解决。

那么问题来了,结合我们一开始的分析,如果我们需要在线性时间内解决这个问题,则我们的压入,弹出,获取最大这三件事都需要O(1)内完成。【这真的可能做到吗?存在这么厉害的数据结构嘛?】

单调队列

答案是能做到的。讨论区一个叫fentoyal的大神(600+的reputation)指出,这是一道典型的单调队列的问题。原文请戳这里

Sliding window minimum/maximum = monotonic queue. I smelled the solution just when I read the title.

这位大牛提供了一份可以直接拿来使用的MonotonicQueue的代码,供我们直接使用。我们来看看这份代码。


Monoqueue

单调队列的操作包括:

push:在队尾压入元素O(1)【这里的O(1)是由于所有的元素都入队一次,出队一次均摊意义上的O(1)】

pop:弹出队首的元素(最大元素)【O(1)】

max:返回队首的最大元素【O(1)】

这份代码使用双向队列deque作为容器存放元素。元素的类型设置为了pair——为每一个元素添加了一个计数器,记录的当前元素和上一个比它大的元素之间的元素个数(下文中我们称为count)。有点拗口(表达能力还需要训练...)。其实简单地说,就是记录在弹出这个元素之前还需要弹出多少个元素,我们不记录这些元素到底是什么(因为他们是什么并不重要,相反是一种浪费),而是记录这样的元素到底有多少个。

举个例子:对于一个队列a[i](ki),a[i+1](ki+1) 【括号中的数字表示count】a[i] >= a[i+1]

其中ki+1表示在a[i] 和a[i+1]中间有多少个元素。我们不关注这些元素,只关注这样的元素有多少个。如此想想,其实我们deque中保存的仅仅是有可能作为max被弹出的元素,因此我们的push操作的速度要比我们想象中快得多。【本身就是均摊意义上的O(1)】

笔者觉得这份实现较为优雅。拿来和大家分享下~侵删。


单调队列的应用

有了这个单调队列,我们可以直接使用它来解决我们这道Sliding Window

代码如下:


accept

我们利用单调队列,讲一个O(nlogn)的问题简化为了O(n).

所以当我们在优化DP问题,维护队列状态(最大最小)的时候,不妨尝试下单调队列


参考文献:

百度百科

最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容