Skip to content

单调队列

模板

滑动窗口问题

给出一个长度为 n 的数组,编程输出每 k 个连续的数中的最大值和最小值。以最小值,k=3 为例。

  • 从左到右遍历,每次把当前元素和队尾元素比较
    • 当前元素更大,入队
    • 当前元素更小,弹出队尾元素(有点单调栈的感觉)
  • 维护一个队列 / 双端队列
    • 自然出队:滑动窗口长度只有 3,弹出队首
    • 比较出队:当前元素更小,弹出队尾元素
  • 队列保持单调递增
  • 队首元素 为滑动窗口内的最小值

例子:

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

queue = 
[1]          # 1 入队
[1, 3]       # 3 入队
[-1]         # 3, 1 比 -1 大,从队尾弹出。-1 入队
[-3]         # -1 比 -3 大,从队尾弹出。-3 入队
[-3, 5]      # 5 入队
[-3, 3]      # 5 比 3 大,从队尾弹出。3 入队
[3, 6]       # -3 超过滑动窗口大小,从队首弹出。6 入队
[3, 6, 7]    # 7 入队
             # [hh, ..., tt]

ans = 
[-1, -3, -3, -3, 3, 3]

模拟队列写法

C++ 代码

取最小值:

  • 队列单调递增(队首到队尾)
  • 队首为最小值
cpp
void getMin() {
    int hh = 0;
    int tt = -1;

    for (int i = 0; i < k - 1; i ++) {
        while (hh <= tt && a[q[tt]] >= a[i]) {
            tt --;
        }

        q[++ tt] = i;
    }

    for (int i = k - 1; i < n; i ++) {
        while (hh <= tt && a[q[tt]] >= a[i]) {
            tt --;
        }

        q[++ tt] = i;

        while (q[hh] <= i - k) {
            hh ++;
        }

        printf("%d ", a[q[hh]]);
    }
}

取最大值:

  • 单调递减(队首到队尾)
  • 队首为最大值
cpp
void getMax() {
    int hh = 0;
    int tt = -1;

    for (int i = 0; i < n; i ++) {
        if (hh <= tt && q[hh] < i - k + 1) {
            hh ++;
        }

        while (hh <= tt && a[q[tt]] <= a[i]) {
            tt --;
        }

        q[++ tt] = i;

        if (i >= k - 1) {
            printf("%d ", a[q[hh]]);
        }
    }
}
  • 一定要有 hh <= tt
  • 之所以用 if 而不是 while 是因为每一轮最多在队首出队一次

STL 写法

  • C++ 需要用到 deque
  • Python 的 list 本身就是双端队列
python

单调队列优化 DP 问题

一般用来维护区间最值

  • 前提:区间右端点变大时,左端点也在变大(同滑动窗口)
  • 转移前,去掉队首无用数据
  • 计算转移(直接从队首转移)
  • f[i] 插入队尾前,去掉队尾无用数据

题目

单调队列求最值等:

单调队列求最值等:

Released under the MIT License.