Skip to content

单调队列 Monotonic Queue

例题POJ 2823 Sliding Window,长度为\(n\)的数组,编程输出每\(k\)个连续的数中的最大和最小值。

笨办法每次都取\(k\)个数来比较,所以复杂度是\(O(nk)\),单调队列方法尽量复用之间做的比较结果,可以做到一遍扫描完成,复杂度O(n)

单调队列算法不变量如下:以找最小值为例,我们维护当前\(k\)个字符窗口中的一个单调增的数字的序列(队列),保证队列头部(最小)的数字是窗口中的最小值。

依次向右扫描新的数字,做两个操作就可以保证这个不变量:

  • 如果扫描时碰到的数字比队列尾部数字大,则加入队列,否则将队尾数字依次删除。
  • 如果队列头部数字不在窗口中,则将头部数字删除。

这样每个数字最多进出队列一次,队列用\(n\)长度的下标数组维护,算法复杂度\(O(n)\)

参考代码

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#define maxn 1000100
using namespace std;
int q[maxn], a[maxn];
int n, k;

void getmin() {  // 得到这个队列里的最小值,直接找到最后的就行了
  int head = 0, tail = 0;
  for (int i = 1; i < k; i++) {
    while (head <= tail && a[q[tail]] >= a[i]) tail--;
    q[++tail] = i;
  }
  for (int i = k; i <= n; i++) {
    while (head <= tail && a[q[tail]] >= a[i]) tail--;
    q[++tail] = i;
    while (q[head] <= i - k) head++;
    printf("%d ", a[q[head]]);
  }
}

void getmax() {  // 和上面同理
  int head = 0, tail = 0;
  for (int i = 1; i < k; i++) {
    while (head <= tail && a[q[tail]] <= a[i]) tail--;
    q[++tail] = i;
  }
  for (int i = k; i <= n; i++) {
    while (head <= tail && a[q[tail]] <= a[i]) tail--;
    q[++tail] = i;
    while (q[head] <= i - k) head++;
    printf("%d ", a[q[head]]);
  }
}

int main() {
  scanf("%d%d", &n, &k);
  for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
  getmin();
  printf("\n");
  getmax();
  printf("\n");
  return 0;
}

习题