Skip to content
Work Practice Products Coding interviews Machine learning coding Quant developer HFT course Blog Method Why me About Enquire Book a call

Coding · Sliding window

The monotone deque

Lesson · 12 min read · Sliding window · Monotone deques

A sum can lose an element in O(1); a maximum cannot. A deque that keeps only the elements that could still become the maximum can, and each element costs O(1) over the whole scan.

Why the maximum is the hard case

Sums and counts undo cleanly: subtract what leaves. A maximum does not. When the largest element leaves the window, the new maximum could be anywhere inside, and scanning for it makes the window O(nk). A heap fixes that in O(log n) per step, with lazy deletion of elements that have left. A monotone deque does it in O(1) amortised.

The observation behind the deque: if an element arrives that is at least as large as an earlier one, the earlier one can never be the maximum again. It is no larger and it leaves the window first. So it can be thrown away immediately. What remains, from front to back, is a list of indices with strictly decreasing values, and the front is the maximum of the window.

The maximum of every window of length 3 in [1, 3, −1, −3, 5, 3, 6, 7].

  1. Step 1 / 8
    nums
    10i31-12-3354356677
    deque (values)
    10
    maxima
    none yet0
    k3

    Index 0 (value 1). The first window is not complete yet.

  2. Step 2 / 8
    nums
    1031i-12-3354356677
    deque (values)
    30
    maxima
    none yet0
    k3

    Index 1 (value 3). It beats 1, which can never be a maximum again, so they leave from the back. The first window is not complete yet.

  3. Step 3 / 8
    nums
    1031-12i-3354356677
    deque (values)
    30-11
    maxima
    30
    k3

    Index 2 (value -1). The front, 3, is the maximum of window [0, 2].

  4. Step 4 / 8
    nums
    1031-12-33i54356677
    deque (values)
    30-11-32
    maxima
    3031
    k3

    Index 3 (value -3). The front, 3, is the maximum of window [1, 3].

  5. Step 5 / 8
    nums
    1031-12-3354i356677
    deque (values)
    50
    maxima
    303152
    k3

    Index 4 (value 5). It beats -3, -1, 3, which can never be a maximum again, so they leave from the back. The front, 5, is the maximum of window [2, 4].

  6. Step 6 / 8
    nums
    1031-12-335435i6677
    deque (values)
    5031
    maxima
    30315253
    k3

    Index 5 (value 3). The front, 5, is the maximum of window [3, 5].

  7. Step 7 / 8
    nums
    1031-12-33543566i77
    deque (values)
    60
    maxima
    3031525364
    k3

    Index 6 (value 6). It beats 3, 5, which can never be a maximum again, so they leave from the back. The front, 6, is the maximum of window [4, 6].

  8. Step 8 / 8
    nums
    1031-12-3354356677i
    deque (values)
    70
    maxima
    303152536475
    k3

    Index 7 (value 7). It beats 6, which can never be a maximum again, so they leave from the back. The front, 7, is the maximum of window [5, 7]. Each index entered and left the deque at most once: O(n) in all.

The code

The deque stores indices, not values. That is how the front can be recognised as having left the window: its index is less than hi − k + 1.

from collections import deque

def window_maximum(nums, k):
    dq = deque()                    # indices; their values fall from front to back
    out = []
    for i, x in enumerate(nums):
        while dq and nums[dq[-1]] <= x:     # dominated: leaves sooner and is no larger
            dq.pop()
        dq.append(i)
        if dq[0] <= i - k:                   # the front has left the window
            dq.popleft()
        if i >= k - 1:
            out.append(nums[dq[0]])
    return out

The same code is the reference solution to Maximum of every window, where it is compiled and run against the tests in all six languages.

TimeO(n)
SpaceO(k)

Each index is pushed once and popped at most once, from the back as dominated or from the front as expired. The inner while loop can run long on one step, but never more than n times in total.

Inside a variable window

The deque is a state like any other, so it plugs into the variable template. "The largest and smallest elements differ by at most a limit" survives shrinking, so it is a longest-window question; its test needs the maximum and the minimum, so keep two deques, one decreasing and one increasing. When lo passes a deque's front index, pop it.

The longest run of [10, 1, 2, 4, 7, 2] whose spread is at most 5.

  1. Step 1 / 6
    nums
    100lo/hi1122437425
    max deque[10]min deque[10]best1

    10 enters. Max 10, min 10, spread 0. Longest so far: 1.

  2. Step 2 / 6
    nums
    10011lo/hi22437425
    max deque[1]min deque[1]best1

    1 enters. Max 10, min 1, spread 9. Over 5, so move lo to 1; the spread is now 0.

  3. Step 3 / 6
    nums
    10011lo22hi437425
    max deque[2]min deque[1, 2]best2

    2 enters. Max 2, min 1, spread 1. Longest so far: 2.

  4. Step 4 / 6
    nums
    10011lo2243hi7425
    max deque[4]min deque[1, 2, 4]best3

    4 enters. Max 4, min 1, spread 3. Longest so far: 3.

  5. Step 5 / 6
    nums
    1001122lo4374hi25
    max deque[7]min deque[2, 4, 7]best3

    7 enters. Max 7, min 1, spread 6. Over 5, so move lo to 2; the spread is now 5.

  6. Step 6 / 6
    nums
    1001122lo437425hi
    max deque[7, 2]min deque[2]best4

    2 enters. Max 7, min 2, spread 5. Longest so far: 4.

Problems in this module