Monotonic Deque
A monotonic deque is a double-ended queue whose contents are kept in monotonically increasing or decreasing order from front to back. It is the natural generalization of Monotonic Stack to settings where elements can leave the structure not only by being “displaced by a larger competitor” (back-end pop, like a monotonic stack) but also by expiring — falling out of a moving window from the front. With this twin-ended behavior, a monotonic deque answers the question “what is the maximum (or minimum) value in the current sliding window of size k?” in O(n) total across the entire stream — beating the heap solution at O(n log k). The pattern is the canonical interview answer for LeetCode 239 (Sliding Window Maximum) and underlies a deep family of problems including LeetCode 862, 1438, 1696, 918, and 1499. The amortized “each element enters and leaves the deque at most once” argument that makes it O(n) is the same argument that powers Monotonic Stack, extended to a structure with two mutating ends instead of one.
1. Intuition — A Lookout Tower with a Strict Hierarchy and a Dismissal Clock
Picture a defensive watchtower in a medieval city. At any moment, soldiers stand in a line on the parapet from front (most senior visible) to back (most junior visible). Two rules govern who’s on the parapet:
-
Hierarchy rule (back-end pop): when a new soldier is sent to the parapet from the barracks, all soldiers already on the parapet who are junior to the newcomer must immediately step down — they’re outranked and therefore unnecessary; the newcomer will outshine them anyway. The newcomer takes the back of the line. After this, the line’s seniorities are strictly decreasing front to back.
-
Dismissal-clock rule (front-end pop): each soldier serves a fixed-length watch (size
k). When a soldier’s watch ends, they step down from the front, regardless of seniority — the clock is absolute. The most senior soldier still on watch is therefore always at the front (rule 1 ensures the front is most senior; rule 2 evicts when the timer expires).
At any moment, the question “who is the most senior soldier currently on watch?” is answered in O(1) by reading the front of the line. Across the entire night, every soldier is added to the line at most once and removed at most once — the total work is bounded by the number of soldiers, regardless of how many “step-down cascades” happened.
This is exactly a monotonic deque tracking the maximum of a sliding window of size k. The “soldiers” are the array’s elements; “seniority” is value; the “watch length” is the window size; “on watch” means “still inside the current window.”
A second analogy useful for the maximum-tracking variant: a leaderboard of athletes that displays only the currently tied-for-or-leading and recently-set records. A new world-record demolishes all previously-displayed lower records (back-end pops). A record older than the season’s cutoff date drops off the front automatically (front-end expiry).
The deep reason this gives O(n) instead of O(n log k): the Binary Heap-based solution treats every window-exit independently, costing O(log k) per insertion/deletion. The monotonic deque exploits the fact that most elements in a window are dominated and will never be the answer; it discards them eagerly at insertion time, so the structure stays small and operations stay O(1) amortized. It’s an example of “lazy deletion done eagerly” — the heap-based version uses lazy deletion (skip stale elements when they bubble up) and pays log-factor; the deque version commits to deleting on entry rather than on exit and gets the log-factor back.
2. Tiny Worked Example — Sliding Window Maximum, k = 3
arr = [4, 2, 1, 5, 3, 6, 8, 0, 7], window size k = 3. Expected output (max in each window of 3 consecutive): [4, 5, 5, 6, 8, 8, 8].
The deque holds indices (not values — we need indices to know when an entry has expired) in strictly decreasing value order from front to back. At each step:
- Expire: while the front index is
≤ i - k, pop from the front. - Maintain hierarchy: while the back’s value is
≤ arr[i], pop from the back. - Push: append
ito the back. - Record: once
i ≥ k - 1, the answer for the window ending atiisarr[deque[0]].
Trace (deque shown as [idx(value), ...], front first):
| i | arr[i] | Expire | Pop back while ≤ arr[i] | Push | Record (when i ≥ 2) |
|---|---|---|---|---|---|
| 0 | 4 | — | empty | [0(4)] | — |
| 1 | 2 | front 0 in window | back 4 > 2, no pop | [0(4), 1(2)] | — |
| 2 | 1 | front 0 in window | back 2 > 1, no pop | [0(4), 1(2), 2(1)] | arr[0]=4 |
| 3 | 5 | front 0 falls out (0 ≤ 3-3=0), pop | 1: 2≤5 pop; 2: 1≤5 pop; deque empty | [3(5)] | arr[3]=5 |
| 4 | 3 | front 3 in window | back 5 > 3, no pop | [3(5), 4(3)] | arr[3]=5 |
| 5 | 6 | front 3 in window | 4: 3≤6 pop; 3: 5≤6 pop; empty | [5(6)] | arr[5]=6 |
| 6 | 8 | front 5 in window | 5: 6≤8 pop; empty | [6(8)] | arr[6]=8 |
| 7 | 0 | front 6 in window | back 8 > 0, no pop | [6(8), 7(0)] | arr[6]=8 |
| 8 | 7 | front 6 falls out (6 ≤ 8-3=5)? 6 > 5, still in window | 7: 0≤7 pop; 6: 8>7 no pop | [6(8), 8(7)] | arr[6]=8 |
Output sequence (i = 2..8): [4, 5, 5, 6, 8, 8, 8]. ✅
Total pushes: 9 (one per index). Total back-pops + front-pops: 5 (indices 0, 1, 2 popped from back; index 3 popped from back; index 4 popped from back). Each index entered at most once and left at most once. Total work O(n).
The naive solution scans each window’s k elements: O(nk) = O(n × k). For n = 10⁶ and k = 10⁴, naive is 10¹⁰ ops; deque is 10⁶ ops. Four orders of magnitude faster.
3. The Pattern Recognition Signal
Reach for monotonic deque when any of these problem shapes appears:
-
“Find the maximum (or minimum) element in every sliding window of size k.” — LC 239 verbatim. The textbook trigger.
-
“Compute, for each index, the (max | min) over the last k (or up to k) preceding elements.” — Bounded look-back over a moving range. Includes DP transitions like
dp[i] = arr[i] + max(dp[i-1], dp[i-2], ..., dp[i-k]), where the max-over-window can be maintained in O(1) instead of O(k) per step (LC 1696 Jump Game VI). -
“Find the longest subarray such that max(subarray) − min(subarray) ≤ limit.” — LC 1438. Maintain two monotonic deques in parallel (one for max, one for min); the diff between deque-fronts gives the current window’s range. Variable-window sliding with two-deque tracking.
-
“Shortest subarray with sum at least K (with negatives allowed).” — LC 862. Keep a monotonic deque on the Prefix Sums array; the answer for each
iis the smallesti - jwithprefix[i] - prefix[j] ≥ K. The deque stores prefix indices in strictly increasing prefix-sum order; at eachi, pop from the back whileprefix[back] ≥ prefix[i](back is dominated as a future candidate), then pop from the front whileprefix[i] - prefix[front] ≥ K(we found an answer ending atiwith start atfront). This problem with negatives is one of the most beautiful applications — sliding window alone fails because negatives break the predicate’s monotonicity, but a monotonic deque on prefix sums saves it. -
“Stream of values; at each step, report the max (or min) of the last k.” — Real-time analytics use case, same algorithm.
-
“DP on a sequence where the transition is max/min over a bounded window.” — LC 1696 Jump Game VI, LC 1425 Constrained Subset Sum, LC 918 Maximum Sum Circular Subarray (variant).
The inverse signals — when monotonic deque is not the right tool:
- The aggregate is median, kth-max, or sum over the window — these are not “decompose to one-best” problems. Use Two Heaps Pattern for medians, sorted multisets for kth-max, Prefix Sums for sums.
- The window is unsized (variable both ends, no length cap) and the predicate is non-monotone — that’s Sliding Window territory plus possibly Hash Table; monotonic deques specifically optimize fixed-size or fixed-direction-bounded windows.
- You need random access into the window’s history — deques are O(n) in the middle. Use Segment Tree or Sparse Table for arbitrary range-min/max queries.
- The values can be re-inserted (ages can come back) — monotonic deques assume each value enters once and leaves once.
4. Pseudocode
The maximum-of-window template (the minimum variant flips the comparator):
sliding_window_max(arr, k):
n := length(arr)
deque := empty deque of indices # maintained: arr-values strictly decreasing front→back
result := empty list
for i in 0 .. n - 1:
# Step 1: expire indices that fell out of the window
while deque is not empty AND deque.front() <= i - k:
deque.pop_front()
# Step 2: maintain monotonic-decreasing invariant
while deque is not empty AND arr[deque.back()] <= arr[i]:
deque.pop_back()
# Step 3: push current index
deque.push_back(i)
# Step 4: record window's max once window has filled
if i >= k - 1:
result.append(arr[deque.front()])
return result
Why each step is correct:
- Step 1 removes any index that is too old to still be in the window of size
kending ati. The window covers[i - k + 1, i]; an indexjis out-of-window iffj ≤ i - k. Only the front needs to be checked (because the deque is strictly increasing in indices — see invariants below). - Step 2 restores monotonicity. If the back’s value is
≤ arr[i], then for any future window containingi, the back is dominated byiand will never be the max — discard it permanently. Repeat until the back is strictly greater thanarr[i]. - Step 3 appends
iat the back. - Step 4 reads the front, which is now the max of the window: the front is the leftmost (oldest, lowest-index) value strictly greater than every value to its right (by the invariant).
Two invariants (maintained at all times after each iteration):
I1. Index ordering: the deque’s indices are strictly increasing front to back. I2. Value ordering (for max-deque): the deque’s values (under the indices) are strictly decreasing front to back.
I1 follows from always appending the current index i to the back (and never inserting elsewhere). I2 follows from Step 2 — by popping the back while it’s ≤ arr[i], after the pop loop the back is strictly greater than arr[i], so appending i keeps values strictly decreasing.
Variant: minimum-of-window. Replace ≤ with ≥ in Step 2; values are then strictly increasing front to back; the front is the min.
Variant: strict vs non-strict. The choice of < vs ≤ in Step 2 affects what happens with ties. Using ≤ (pop on equality) means the deque holds strictly distinct values; the front is unique. Using < (don’t pop on equality) means ties are preserved; the front is still a valid max. For most interview problems either works.
5. Python Implementations
5.1 Sliding Window Maximum (LeetCode 239)
from collections import deque
def max_sliding_window(nums: list[int], k: int) -> list[int]:
if not nums or k <= 0:
return []
dq: deque[int] = deque() # holds indices; values strictly decreasing front→back
result: list[int] = []
for i, x in enumerate(nums):
# Expire out-of-window indices from the front
while dq and dq[0] <= i - k:
dq.popleft()
# Maintain monotonic decreasing invariant from the back
while dq and nums[dq[-1]] <= x:
dq.pop()
dq.append(i)
# Record once the window is full
if i >= k - 1:
result.append(nums[dq[0]])
return resultThe four steps from the pseudocode map line-for-line. The <= in the back-pop loop discards equal values too — keeping the deque strictly decreasing. Replacing it with < would keep ties; both are correct, just different deque sizes in pathological cases.
5.2 Shortest Subarray with Sum at Least K (LeetCode 862) — Hard Variant
Given an integer array (with possibly negative elements) and an integer K, return the length of the shortest contiguous subarray whose sum is ≥ K, or -1.
The naive sliding window fails on negatives (you can’t shrink-while-valid because a future negative could rescue or violate). The fix: precompute Prefix Sums, then the question is “for each i, what is the smallest i - j (with j ≤ i) such that prefix[i] - prefix[j] ≥ K?” Maintain a monotonic deque of prefix indices in strictly increasing prefix-sum order:
from collections import deque
def shortest_subarray(nums: list[int], k: int) -> int:
n = len(nums)
prefix = [0] * (n + 1)
for i in range(n):
prefix[i + 1] = prefix[i] + nums[i]
dq: deque[int] = deque() # holds prefix-array indices, prefix values strictly increasing front→back
best = n + 1
for i in range(n + 1):
# Step A: while deque-front is a valid start (prefix[i] - prefix[front] ≥ k), record and shrink from front
while dq and prefix[i] - prefix[dq[0]] >= k:
best = min(best, i - dq.popleft())
# Step B: while deque-back has prefix ≥ prefix[i], it's dominated: any future i' that satisfies prefix[i']-prefix[back]≥k
# also satisfies prefix[i']-prefix[i]≥k with smaller length. Pop.
while dq and prefix[dq[-1]] >= prefix[i]:
dq.pop()
dq.append(i)
return best if best <= n else -1The two pop loops are doing different jobs:
- Step A is the answer-extraction pop: when an old
jalready satisfies the K-sum constraint with the currenti, record the candidate lengthi - jand removejfrom the deque (any futurei' > iwould only give worse — longer — answers using the samej, since we want shortest). - Step B is the dominance pop: an older
jwithprefix[j] ≥ prefix[i]is dominated as a future candidate (because usingias the start gives a smaller length and a larger gap to clearK).
Both popping rules combined keep the deque size bounded; each prefix index enters at most once and leaves at most once. Total O(n).
5.3 Jump Game VI (LeetCode 1696) — DP with Bounded-Window Max
Given nums and k, you start at index 0 and at each step jump to any index in [i+1, i+k]. Maximize the sum of values landed on, including start and end. Define dp[i] = max score to reach index i. Recurrence:
dp[i] = nums[i] + max(dp[i-1], dp[i-2], ..., dp[i-k])
Naive: O(nk). With monotonic deque maintaining the max of the last k dp values: O(n).
from collections import deque
def max_result(nums: list[int], k: int) -> int:
n = len(nums)
dp = [0] * n
dp[0] = nums[0]
dq: deque[int] = deque([0]) # indices of dp, values strictly decreasing front→back
for i in range(1, n):
while dq and dq[0] < i - k:
dq.popleft()
dp[i] = nums[i] + dp[dq[0]]
while dq and dp[dq[-1]] <= dp[i]:
dq.pop()
dq.append(i)
return dp[n - 1]This is the most general “DP-with-bounded-window-max” speedup pattern. Whenever a DP recurrence has a max (or min) over a bounded suffix of the table, this trick collapses an O(nk) DP to O(n).
5.4 Longest Subarray With Absolute Diff ≤ Limit (LeetCode 1438) — Two Deques
Find the longest contiguous subarray such that max(subarray) - min(subarray) ≤ limit. Maintain two monotonic deques: a max-deque (decreasing) and a min-deque (increasing). Variable-size sliding window: while the current window’s max − min exceeds limit, advance left.
from collections import deque
def longest_subarray(nums: list[int], limit: int) -> int:
max_dq: deque[int] = deque() # decreasing
min_dq: deque[int] = deque() # increasing
left = 0
best = 0
for right, x in enumerate(nums):
while max_dq and nums[max_dq[-1]] <= x:
max_dq.pop()
max_dq.append(right)
while min_dq and nums[min_dq[-1]] >= x:
min_dq.pop()
min_dq.append(right)
# Shrink from left while max - min > limit
while nums[max_dq[0]] - nums[min_dq[0]] > limit:
left += 1
if max_dq[0] < left:
max_dq.popleft()
if min_dq[0] < left:
min_dq.popleft()
best = max(best, right - left + 1)
return bestThe deques are now indexed by absolute position (not window-relative), so when the window’s left advances past a deque’s front index, the front gets evicted. This is the same expiration mechanism as LC 239, but driven by a variable left rather than a fixed window length.
6. Complexity — The Amortized O(n) Argument, Two-Ended Version
Time: O(n) total.
The amortized argument extends the Monotonic Stack one (which had only back-end pops) to two-ended pops. Define potential Φ as the current deque size. In each iteration i of the outer loop:
- The front-pop loop runs
f_itimes (front-end evictions due to expiry). - The back-pop loop runs
b_itimes (back-end evictions due to dominance). - Exactly one push (
+1to size).
Real cost of iteration: 1 + f_i + b_i. Change in potential: +1 - f_i - b_i. Amortized cost = real cost + ΔΦ = 2.
Total amortized cost across n iterations = 2n. Plus a final flush of remaining deque contents (≤ n more pops). Total real cost ≤ 3n = O(n).
Equivalently: each input element is pushed at most once (n pushes total) and popped at most once (≤ n pops total, summed across both ends). Comparisons and assignments are O(1) per push/pop. Total work O(n).
Space: O(k) for the deque. The deque can hold at most k indices simultaneously (any older index has expired). For LC 862 and other variable-window variants, space is O(n) worst-case.
Comparison with the heap approach for LC 239:
| Approach | Time | Space | Notes |
|---|---|---|---|
| Naive (scan each window) | O(nk) | O(1) | Trivial; too slow for k near n |
| Binary Heap (max-heap with lazy deletion) | O(n log n) | O(n) | Heap can grow to n; pop stale on top |
| Binary Heap (max-heap with index-tracked deletion) | O(n log k) | O(k) | Requires augmented heap |
| Monotonic deque | O(n) | O(k) | Optimal; the canonical answer |
The deque wins on time and space among O(n)-space solutions. The heap solution is conceptually clean and generalizes to “kth max” (which the deque doesn’t), so it’s worth knowing both — but for the strict “max in each window” problem, the deque is strictly better and is the answer interviewers expect.
7. Variants and Sub-patterns
7.1 Sliding Window Median, Kth Max, Histogram
The deque trick does not generalize to median or kth-max, because those depend on more than the single dominating element. For median, use Two Heaps Pattern; for kth max, use a sorted multiset (SortedList in Python); for full sliding-window order statistics, use a Segment Tree or Fenwick Tree indexed by value.
7.2 First Negative Number in Every Window (GFG classic)
A specialization: for each window, find the first negative number. Maintain a deque of indices of negative numbers; the front is the answer. Same expiry logic.
7.3 Maximum of All Subarrays of Size K with Threshold
Variants like “count windows where max ≥ T” trivially fold the deque output through a counter.
7.4 Constrained Subset Sum (LC 1425)
dp[i] = nums[i] + max(0, max(dp[i-k..i-1])). Same window-max DP optimization as Jump Game VI, with a small max(0, ...) twist.
7.5 Largest Sum Subarray of Length at Most K (variant)
When the constraint is “length ≤ K” rather than ”= K,” combine Prefix Sums with a sliding-window-min on prefix array. The deque holds prefix indices in increasing prefix-sum order; the answer at index i uses prefix[i] - min(prefix[i-k..i-1]).
7.6 Minimum Window Subarray with Sum > Threshold (LC 209 variant)
For non-negative arrays, plain Sliding Window works. For arrays with negatives, monotonic deque on prefix sums (LC 862-style) is required.
7.7 Equivalent Persistent Structure: Minimum Queue
Mikhail Cherniavski’s “minimum queue” construction implements a queue augmented with O(1) min-query (or max-query) by composing two monotonic stacks. The two-stack-amortized-queue (LC 232) plus the monotonic-stack invariant on each stack gives O(1) amortized push/pop and O(1) min-query, structurally identical in capability to a monotonic deque.
8. Pitfalls
8.1 Storing Values Instead of Indices
You need indices to detect expiry (deque[0] <= i - k). Storing values and tracking the front separately is possible but error-prone; store indices, recover values via arr[deque[0]].
8.2 Off-By-One in the Window-Expiry Check
For a window of size k ending at index i, the window covers [i - k + 1, i]. An index j is outside the window iff j < i - k + 1, i.e., j ≤ i - k. The check is deque[0] <= i - k, not deque[0] < i - k + 1 (equivalent) and not deque[0] < i - k (off by one). Trace by hand on i = k - 1 (first complete window) and i = k (second complete window) to verify.
8.3 Recording Before the Window Is Full
For LC 239, results are recorded only for i ≥ k - 1. Recording earlier produces output for incomplete windows and inflates the array length. Easy to omit and silently ship a wrong-length output.
8.4 Wrong Pop Comparator (< vs ≤)
Using < keeps duplicates; using ≤ discards them. For pure max queries either is correct, but for “how many duplicate maxes?” or “first occurrence of max” the choice matters. Default to ≤ for the simpler invariant of strictly-decreasing values, unless the problem demands tie preservation.
8.5 Forgetting the Front Pop When the Window Slides
If you only do back-pops (the Monotonic Stack pattern), expired elements at the front sit in the deque indefinitely and pollute the answer. Always check the front for expiry before using it. The order — expire first, then push — is part of the invariant.
8.6 Mishandling Negatives in Sliding-Window-Sum Variants
For LC 862 (negatives allowed), naive sliding window fails. The monotonic-deque-on-prefix-sums fix is non-obvious. If the array has negatives and you reach for a sliding window, stop — switch to prefix-sums-plus-deque (or a sorted-multiset approach).
8.7 Confusing With Monotonic Stack
The choice between Monotonic Stack and Monotonic Deque is determined by whether elements expire from the opposite end of where they enter. If you insert at the back and only pop dominated elements from the back (no front-end expiry), you have a monotonic stack. If you also pop expired elements from the front, you have a monotonic deque. The problem statement is the cue: any “sliding window” or “last k” or “bounded look-back” hint means deque.
8.8 Not Using Python’s collections.deque
list.pop(0) is O(n) (see Queue §7.1). A monotonic deque written on a Python list is not O(n) total — it’s O(n²). Always use from collections import deque for monotonic-deque algorithms.
9. Diagram — Deque State Through [4, 2, 1, 5, 3, 6] with k = 3
flowchart TD S0["i=0, x=4\nexpire: none\nback-pop: empty\npush 0\nDQ=[0(4)]"] S1["i=1, x=2\nexpire: front 0, 0≤-2? no\nback-pop: 4>2 stop\npush 1\nDQ=[0(4), 1(2)]"] S2["i=2, x=1\nexpire: 0≤-1? no\nback-pop: 2>1 stop\npush 2\nDQ=[0(4), 1(2), 2(1)]\nWindow [4,2,1] max=4"] S3["i=3, x=5\nexpire: 0≤0? yes, popleft\nback-pop: 1: 2≤5 pop; 2: 1≤5 pop; empty\npush 3\nDQ=[3(5)]\nWindow [2,1,5] max=5"] S4["i=4, x=3\nexpire: 3≤1? no\nback-pop: 5>3 stop\npush 4\nDQ=[3(5), 4(3)]\nWindow [1,5,3] max=5"] S5["i=5, x=6\nexpire: 3≤2? no\nback-pop: 4: 3≤6 pop; 3: 5≤6 pop; empty\npush 5\nDQ=[5(6)]\nWindow [5,3,6] max=6"] S0 --> S1 --> S2 --> S3 --> S4 --> S5
What this diagram shows. Each box reports one outer-loop iteration: the index i, the value arr[i], the front-expiry check (with i - k as the threshold; entries with index ≤ i - k get popped from the front), the back-pop cascade (popping every back-end value ≤ arr[i]), the resulting deque (entries shown as idx(value), front first), and the window’s maximum. Notice two key moments: at i = 3, the front entry 0(4) expires and the entire remainder of the deque (1, 2) is dominated by the new value 5, leaving only [3(5)] — a “full clean-out.” At i = 5, again all back entries are dominated, giving [5(6)]. Visually, the deque oscillates between long-decreasing-runs (during a downward stretch of the input) and small-or-empty (right after a new run-maximum). The total number of pushes (6) plus the total number of pops (4 across both ends in this trace) sum to 2n - 2 = 10 — a constant-bounded multiple of n. That’s the amortized O(n) argument made tangible.
10. Common Interview Problems
| # | Problem | Variant |
|---|---|---|
| LC 239 | Sliding Window Maximum | Canonical: max in fixed window |
| LC 1438 | Longest Subarray with Abs Diff ≤ Limit | Two deques (max + min) |
| LC 862 | Shortest Subarray with Sum at Least K | Deque on prefix sums (negatives OK) |
| LC 1696 | Jump Game VI | DP with bounded-window max |
| LC 1425 | Constrained Subset Sum | DP with bounded-window max + nonneg cap |
| LC 918 | Maximum Sum Circular Subarray | Variants combine deque with Kadane |
| LC 1499 | Max Value of Equation | Deque on (y - x, x) candidates |
| LC 1944 | Number of Visible People in a Queue | Variant — usually monotonic stack, but illustrative |
| LC 1124 | Longest Well-Performing Interval | Prefix sums + monotonic stack/deque trick |
| GFG | First Negative in Every Window of Size K | Specialization of LC 239 |
| LC 480 | Sliding Window Median | NOT monotonic deque — use Two Heaps Pattern (contrast) |
11. Open Questions
- Is there a sub-O(n) algorithm for the offline “max of every window of size k” problem if the input has special structure (e.g., bounded value range)? For the general comparison-based case, O(n) is optimal — every element must be inspected at least once.
- The “minimum queue” via two monotonic stacks (Cherniavski) achieves the same complexity — when does it outperform the direct deque construction in practice? Cache-locality benchmarks would clarify.
- For 2D sliding windows (k×k square), the standard approach is to apply a 1D monotonic deque first along rows, then along columns. Is there a direct 2D monotonic-deque construction with better constants?
- In an online setting with adversarial inputs that force the deque to grow to size k (a strictly decreasing input), is there a way to compress the deque further? Probably not without losing the O(1) amortized push.
12. See Also
- Deque — the underlying data structure
- Monotonic Stack — sister pattern for “next greater/smaller” in non-windowed settings; same amortization argument
- Sliding Window — broader pattern; monotonic deque is its O(n) optimizer for max/min queries
- Binary Heap — alternative for sliding window max at O(n log k); inferior but more general
- Two Heaps Pattern — for sliding window median (where deque doesn’t apply)
- Prefix Sums — combined with monotonic deque for LC 862 and friends
- Two Pointers — alternative for some symmetric problems
- Big-O Notation — for the amortized argument
- SWE Interview Preparation MOC