Monotone Queue

Introduction

Before learning about monotone queues, let us first look at an example problem.

Example

Sliding Window

Given an array of length nn, output the maximum and minimum among every kk consecutive values.

The brute-force idea is simple: for each sequence i∼i+k−1i \sim i+k-1, compare its values one by one to find the maximum (and minimum), with time complexity about O(n×k)O(n \times k).

Clearly, this performs a great deal of repeated work. Except for the first k−1k-1 and last k−1k-1 values, every value is compared kk times. Since 100%100\% of the test data has n≤1000000n \le 1000000, this will obviously TLE when kk is large.

This is where the monotone queue is useful.

Definition

As its name suggests, a monotone queue has two key features: “monotone” and “queue”.

“Monotone” refers to the pattern of its elements—either increasing or decreasing.

“Queue” means that elements can be operated on only at the front and the back.

P.S. The “queue” in a monotone queue differs somewhat from an ordinary queue, as we will see shortly.

Example analysis

Explanation

With the concept of a monotone queue above, it is natural to optimize the problem using one.

We need the maximum (or minimum) of every consecutive kk values. When a value enters the range whose maximum is being sought, if it is greater than a value already ahead of it in the queue, that earlier value will leave the queue first and can no longer be the maximum.

In other words, when this condition holds, pop the earlier values and then push the new value onto the back of the queue.

This maintains a decreasing queue and reduces repeated comparisons. Moreover, because the maintained queue contains exactly the candidates in the query range and is decreasing, its front is necessarily the maximum in that range, so outputting the front is enough.

Clearly, each value enters and leaves the queue exactly once, reducing the time complexity to O(n)O(n).

Because the query interval has fixed length, even a large value outside the query window cannot be output. We therefore also need a site array recording the position of the value at position ii in the queue in the original array, so that an out-of-range front can be removed.

Process

For example, constructing an increasing monotone queue proceeds as follows:

The original sequence is:

1 3 -1 -3 5 3 6 7

Because we always maintain the queue so that it remains increasing, the following occurs (assume k=3k = 3):

OperationQueue state
1 enters the queue{1}
3 is greater than 1, so 3 enters{1 3}
-1 is smaller than every queue element, so clear the queue and insert -1{-1}
-3 is smaller than every queue element, so clear the queue and insert -3{-3}
5 is greater than -3, so insert it directly{-3 5}
3 is smaller than 5, so remove 5 and insert 3{-3 3}
-3 is already outside the window, so remove it; 6 is greater than 3, so insert 6{3 6}
7 is greater than 6, so insert it{3 6 7}
Reference code
#include <cstdlib>
#include <cstring>
#include <iostream>
constexpr int MAXN = 1000100;
using namespace std;
int q[MAXN], a[MAXN];
int n, k;

void getmin() {  // Obtain the minimum of this queue; it is at the back
  int head = 0, tail = -1;
  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++;
    cout << a[q[head]] << ' ';
  }
}

void getmax() {  // The maximum is handled analogously
  int head = 0, tail = -1;
  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++;
    cout << a[q[head]] << ' ';
  }
}

int main() {
  cin.tie(nullptr)->sync_with_stdio(false);
  cin >> n >> k;
  for (int i = 1; i <= n; i++) cin >> a[i];
  getmin();
  cout << '\n';
  getmax();
  cout << '\n';
  return 0;
}

P.S. The main difference between this “queue” and an ordinary queue is that it supports operations at the back as well. The STL has a similar data structure, deque.

Example 2 Luogu P2698 Flowerpot S

Given the coordinates of NN water drops, where yy is the height of a drop and xx is the position where it falls onto the xx-axis. Each drop falls at a speed of one unit of length per second. Place a flowerpot on the xx-axis so that the time from the first drop caught by the pot to the last drop caught by it is at least DD. We consider a drop caught as soon as it reaches the xx-axis and aligns with the edge of the pot. Given the coordinates of NN drops and DD, compute the minimum width WW of the pot. 1≤N≤100000,1≤D≤1000000,0≤x,y≤1061\leq N \leq 100000 , 1 \leq D \leq 1000000, 0 \leq x,y\leq 10^6.

After sorting all drops by their xx-coordinates, the problem becomes finding an interval with the smallest difference in xx-coordinates such that the difference between the maximum and minimum yy-coordinates in the interval is at least DD. This resembles the previous problem because both involve the maximum and minimum in an interval, but here the interval length is unknown and is itself the answer.

We can still use one increasing and one decreasing monotone queue, moving RR rightward while maintaining the maximum and minimum in [L,R][L,R]. For fixed LL, the maximum in [L,R][L,R] only increases and the minimum only decreases, so if f(R)=max⁡[L,R]−min⁡[L,R]f(R) = \max[L,R]-\min[L,R], then f(R)f(R) is increasing in RR. Therefore, f(R)≥D  ⟹  f(r)≥D,R<r≤Nf(R)\geq D \implies f(r)\geq D,R\lt r \leq N. For each fixed LL, the first RR to the right satisfying the condition is optimal. So the overall procedure is to fix LL, move RR from left to right, and use two monotone queues to maintain the extrema in [L,R][L,R]. Once the first valid RR is found, update the answer and move LL rightward as well. As LL advances, remove outdated fronts from both queues. Until RR reaches the end, each element still enters and leaves each queue once, giving O(n)O(n) time complexity.

Reference code
#include <algorithm>
#include <iostream>
using namespace std;
constexpr int N = 100005;
using ll = long long;
int mxq[N], mnq[N];
int D, ans, n, hx, rx, hn, rn;

struct la {
  int x, y;

  bool operator<(const la &y) const { return x < y.x; }
} a[N];

int main() {
  cin.tie(nullptr)->sync_with_stdio(false);
  cin >> n >> D;
  for (int i = 1; i <= n; ++i) cin >> a[i].x >> a[i].y;
  sort(a + 1, a + n + 1);
  hx = hn = 1;
  ans = 2e9;
  int L = 1;
  for (int i = 1; i <= n; ++i) {
    while (hx <= rx && a[mxq[rx]].y < a[i].y) rx--;
    mxq[++rx] = i;
    while (hn <= rn && a[mnq[rn]].y > a[i].y) rn--;
    mnq[++rn] = i;
    while (L <= i && a[mxq[hx]].y - a[mnq[hn]].y >= D) {
      ans = min(ans, a[i].x - a[L].x);
      L++;
      while (hx <= rx && mxq[hx] < L) hx++;
      while (hn <= rn && mnq[hn] < L) hn++;
    }
  }
  if (ans < 2e9)
    cout << ans << '\n';
  else
    cout << "-1\n";
  return 0;
}

This content comes from OI Wiki: Monotone Queue. See the original for authors and revision history. AIOJ has adapted the formatting, in-site links, and resource paths.

Content is shared under CC BY-SA 4.0 and SATA license; additional terms are governed by the original notice. Thanks to the OI Wiki authors and contributors; per the SATA terms, please visit the OI Wiki repository and star it to show your support.