单调队列学习笔记
单调队列是什么
与单调栈类似,单调队列同样本质上仍任是一个队列,不过队列中的元素是有序的。
单调队列用来干什么
众所周知,单调队列主要用来解决滑动窗口问题。
滑动窗口问题所指的是在给定的序列 中寻找所有长度为 连续区域 内的最大值 / 最小值问题。
而使用单调队列解决滑动窗口的时间复杂度为 ,比 ST 表、线段树、树状数组、平衡树更优。
单调队列的思想
以洛谷 P1886 滑动窗口 /【模板】单调队列为例子介绍。
创建两个deque
作为单调队列(一个存储最大值,另一个最小值)。
样例: 当我们读入整个序列之后,开始遍历,初始时 的位置如下:
a: \colorbox{RoyalBlue}{\color{Red}{1}} \quad 3 \quad -1 \quad -3 \quad 5 \quad 3 \quad 6 \quad 7 \newline deque_{max}: \text{empty} \newline deque_{min}: \text{empty}由于此时的长度还不满足 ,并且两个deque
都为空,所以将 的下表1
加入两个deque
。接着将i
自增。
为什么是添加下标到队列中而不是值?
为了在长度超过 时对队列进行元素弹出,所以添加下标进入队列中以便判断,并且也可以通过下标访问对应的值,但通过值查找下标就显得十分不便。
当我们将要把 加入dequeMax
时,发现里边已经非空,于是进行以下操作:
- 判断新加入的 与
deque
的最后一位 对应的 进行比较,如果 就将 弹出,并且重复此操作直到 。 - 加入 的下标
i
到deque
中。
最小值同理。
为啥要又弹又加的?
为了确保两个deque
中元素之中按照一定的顺序进行排列,我们在添加之前需要将不符合顺序的元素弹出(当然因为我们求的是最大值/最小值,所以弹出不是最大值/最小值的元素对答案并无影响)。
进行完以上操作后结果如下:
a: \colorbox{RoyalBlue}{1 \quad \color{Red}{3}} \quad -1 \quad -3 \quad 5 \quad 3 \quad 6 \quad 7 \newline deque_{max}: 2 \newline deque_{min}: 1继续进行下一次操作,操作完成之后:
a: \colorbox{RoyalBlue}{1 \quad 3 \quad \color{Red}{-1}} \quad -3 \quad 5 \quad 3 \quad 6 \quad 7 \newline deque_{max}: 2 \newline deque_{min}: 3继续!
a: \colorbox{Salmon}{1} \quad \colorbox{RoyalBlue}{3 \quad -1 \quad \color{Red}{-3}} \quad 5 \quad 3 \quad 6 \quad 7 \newline deque_{max}: 2 \newline deque_{min}: 3此时发现长度大于 了,进行以下操作:
- 先判断 加入
deque
中。 - 如果
deque
的开头的元素的下标 小于等于 ,说明该下标所对应的值不再被包含在区间之内,将 从deque
中弹出。
最小值同理。
那么如何维护答案呢? 我们惊讶的发现,当 时,需要输出此时的答案,那么此时的答案也正好为两个deque
的队列头部。 所以将他们添加到答案序列ans
中(并不弹出)。
最后是每一步的状态示意:
a: \colorbox{RoyalBlue}{1 \quad 3 \quad \color{Red}{-1}} \quad -3 \quad 5 \quad 3 \quad 6 \quad 7 \newline deque_{max}: 2 \newline deque_{min}: 3 \newline ans_{max}: a_{2} \newline ans_{min}: a_{3} a: 1 \quad \colorbox{RoyalBlue}{3 \quad -1 \quad \color{Red}{-3}} \quad 5 \quad 3 \quad 6 \quad 7 \newline deque_{max}: 2 \newline deque_{min}: 4 \newline ans_{max}: a_{2} \quad a_{2} \newline ans_{min}: a_{3} \quad a_{4} a: 1 \quad 3 \quad \colorbox{RoyalBlue}{-1 \quad -3 \quad \color{Red}{5}} \quad 3 \quad 6 \quad 7 \newline deque_{max}: 5 \newline deque_{min}: 4 \newline ans_{max}: a_{2} \quad a_{2} \quad a_{5} \newline ans_{min}: a_{3} \quad a_{4} \quad a_{4} a: 1 \quad 3 \quad -1 \quad \colorbox{RoyalBlue}{-3 \quad 5 \quad \color{Red}{3}} \quad 6 \quad 7 \newline deque_{max}: 5 \newline deque_{min}: 4 \newline ans_{max}: a_{2} \quad a_{2} \quad a_{5} \quad a_{5} \newline ans_{min}: a_{3} \quad a_{4} \quad a_{4} \quad a_{4} a: 1 \quad 3 \quad -1 \quad -3 \quad \colorbox{RoyalBlue}{5 \quad 3 \quad \color{Red}{6}} \quad 7 \newline deque_{max}: 7 \newline deque_{min}: 6 \newline ans_{max}: a_{2} \quad a_{2} \quad a_{5} \quad a_{5} \quad a_{7} \newline ans_{min}: a_{3} \quad a_{4} \quad a_{4} \quad a_{4} \quad 1_{6} a: 1 \quad 3 \quad -1 \quad -3 \quad 5 \quad \colorbox{RoyalBlue}{3 \quad 6 \quad \color{Red}{7}} \newline deque_{max}: 8 \newline deque_{min}: 6 \newline ans_{max}: a_{2} \quad a_{2} \quad a_{5} \quad a_{5} \quad a_{7} \quad a_{8} \newline ans_{min}: a_{3} \quad a_{4} \quad a_{4} \quad a_{4} \quad 1_{6} \quad a_{6}最后的答案序列也就是:
ans_{max}: 3 \quad 3 \quad 5 \quad 5 \quad 6 \quad 7 \newline ans_{min}: -1 \quad -3 \quad -3 \quad -3 \quad 3 \quad 3code:
1 |
|
时间复杂度的小小说明
什么?你说for
里套了一个while
复杂度就是 了? 非也非也。 deque
中的数是我们之前所添加进来的,也就是说每一个数只会进入 deque 中一次,所以只要while
或者不在 的范围内了就会被弹出。 所以即使while
在for
里,单调队列的时间复杂度依然是 。
什么?神犇你说太简单了?那么来点更刺激的行不行?
二维滑动窗口
以洛谷 P2216 [HAOI2007] 理想的正方形为例,来看看二维的滑动窗口应该如何是好。
我们需要在一个二维的矩阵中计算最大值与最小值,显然可以使用滑动窗口来解决,但是现在还不是说 “就这?” 的时候,我们仍然需要仔细思考这个二维的滑动窗口问题。
如果将一维的滑动窗口模版套上你会发现这是行不通的。
为什么行不通?
当我们直接使用一维的模版时,每一个元素不仅仅进入队列中一次,而是多次进入队列(当需要更换行数,比如从第二行到第三行时),导致性能下降。
经过我们的苦思冥想,终于发现:我们并不需要直接进行二维的滑动窗口,一维一维的做不就行了?
首先在这个 的矩阵中,我们需要找出 的一个区域(其实 ,这一题的 ),计算最大最小值。
为了求出 到 这段区间的最大最小值:
\begin{matrix} 1 & 2 & 5 & 6 \\ 0 & \colorbox{RoyalBlue}{\color{Red}{17}} & \colorbox{RoyalBlue}{\color{Red}{16}} & 0 \\ 16 & \colorbox{RoyalBlue}{\color{Red}{17}} & \colorbox{RoyalBlue}{\color{Red}{2}} & 1 \\ 2 & 10 & 2 & 1 \\ 1 & 2 & 2 & 2 \end{matrix}我们需要分别知道 到 的最大最小值,与 到 的最大最小值即可。
\begin{matrix} 1 & 2 & 5 & 6 \\ 0 & \colorbox{Orange}{\color{Red}{17}} & \colorbox{Orange}{\color{Red}{16}} & 0 \\ 16 & \colorbox{Purple}{\color{Red}{17}} & \colorbox{Purple}{\color{Red}{2}} & 1 \\ 2 & 10 & 2 & 1 \\ 1 & 2 & 2 & 2 \end{matrix}将样例的进行每一行的单调队列处理之后的结果:
max_{row_{i, j}} \gets \max_{x \in \left(i - n, i\right]}\left(num_{x, j}\right) \newline max_{row}: \quad \begin{matrix} 0 & 2 & 5 & 6 \\ 0 & 17 & 17 & 16 \\ 0 & 17 & 17 & 2 \\ 0 & 10 & 10 & 2 \\ 0 & 2 & 2 & 2 \end{matrix} max_{row_{i, j}} \gets \min_{x \in \left(i - n, i\right]}\left(num_{x, j}\right) \newline min_{row}: \quad \begin{matrix} 0 & 1 & 2 & 5 \\ 0 & 0 & 16 & 0 \\ 0 & 16 & 2 & 1 \\ 0 & 2 & 2 & 1 \\ 0 & 1 & 2 & 2 \end{matrix}在此基础上构建每一列的滑动窗口:
max_{column_{i, j}} \gets \max_{x \in \left(j - n, j\right]}\left(max_{row_{i, x}}\right) \newline max_{column}: \quad \begin{matrix} 0 & 0 & 0 & 0 \\ 0 & 17 & 17 & 16 \\ 0 & 17 & 17 & 16 \\ 0 & 17 & 17 & 2 \\ 0 & 10 & 10 & 2 \end{matrix} min_{column_{i, j}} \gets \min_{x \in \left(j - n, j\right]}\left(min_{row_{i, x}}\right) \newline min_{column}: \quad \begin{matrix} 0 & 0 & 0 & 0 \\ 0 & 0 & 2 & 0 \\ 0 & 0 & 2 & 0 \\ 0 & 2 & 2 & 1 \\ 0 & 1 & 2 & 1 \end{matrix}所以ans
的结果:
所以二维滑动窗口简要步骤如下:
- 先对二维的其中一维进行滑动窗口处理。
- 接着在另一维对已经处理过的那一维进行再次处理。
code:
1 |
|
最后再来亿点点练习
- 洛谷 P1886 滑动窗口 /【模板】单调队列
- 洛谷 P1440 求 m 区间内的最小值
- 洛谷 P2032 扫描
- 洛谷 P2251 质量检测