A monotonic queue (or monotonic stack) maintains elements in sorted order (increasing or decreasing) by removing elements that violate the monotonic property when new elements are added.
The key insight: elements that can never be useful are discarded immediately.
| Structure | Remove from | Use case |
|---|---|---|
| Monotonic Stack | Same end as insert (LIFO) | "Next greater/smaller element" |
| Monotonic Queue | Both ends (deque-based) | "Sliding window max/min" |
Both maintain monotonic order; the difference is whether you need to expire old elements (queue) or just find relationships (stack).
A monotonic queue is needed when elements must expire - i.e., sliding window problems. You remove from both ends:
- Back: Remove smaller elements (they can't be max while larger element exists)
- Front: Remove expired elements (outside the window)
Array: [5, 3, 4, 1, 2], k=3
Queue stores indices. Front is always the window maximum.
i=0 (5): push 0 queue=[0]
i=1 (3): 3 < 5, push 1 queue=[0,1]
i=2 (4): 4 > 3, pop 1; 4 < 5, push 2 queue=[0,2] → window[0-2], max=arr[0]=5
i=3 (1): 1 < 4, push 3 queue=[0,2,3]
window is [1-3], idx 0 < 1 → EXPIRED, pop front!
queue=[2,3] → max=arr[2]=4
i=4 (2): 2 > 1, pop 3; 2 < 4, push 4 queue=[2,4]
window is [2-4], idx 2 >= 2 → valid → max=arr[2]=4
Output: [5, 4, 4] – Length of final result is `n - k + 1`.
Why queue, not stack? The front removal for expiration (see i=3). A stack can't efficiently remove from the front.
Stack holds indices of elements waiting for their next greater. When a new element is greater than the stack top, we've found the answer for that top.
Array: [2, 1, 2, 4, 3]
Find: next greater element for each
Process left to right, maintain decreasing stack (of indices):
i=0 (2): stack empty, push 0 stack=[0]
i=1 (1): 1 < arr[0]=2, push 1 stack=[0,1]
i=2 (2): 2 > arr[1]=1 → pop 1, ans[1]=2; push 2 stack=[0,2]
i=3 (4): 4 > arr[2]=2 → pop 2, ans[2]=4
4 > arr[0]=2 → pop 0, ans[0]=4; push 3 stack=[3]
i=4 (3): 3 < arr[3]=4, push 4 stack=[3,4]
End: remaining indices have no next greater ans[3]=-1, ans[4]=-1
Result: [4, 2, 4, -1, -1]
Why decreasing? Elements in the stack are waiting for something bigger. If a new element is smaller, it also needs to wait → push it. If bigger, it's the answer → pop and record.
| Operation | Amortized Time | Notes |
|---|---|---|
push() |
O(1) |
Each element pushed/popped at most once |
pop() |
O(1) |
Remove from front |
max() / min() |
O(1) |
Front of queue |
Overall: Processing n elements takes O(n) total, not O(n²).
Space: O(n) worst case (when input is already sorted in desired order)
| Pattern | Example Problem |
|---|---|
| "Next greater element" | Next Greater Element I, II (LC 496, 503) |
| "Next smaller element" | Daily Temperatures (LC 739) |
| "Previous greater/smaller" | Process left-to-right instead |
| Histogram problems | Largest Rectangle in Histogram (LC 84) |
| Stock span problems | Online Stock Span (LC 901) |
Trigger phrases: "next greater", "next smaller", "how many days until", "span of consecutive"
| Pattern | Example Problem |
|---|---|
| "Sliding window maximum" | Sliding Window Maximum (LC 239) |
| "Sliding window minimum" | Shortest Subarray with Sum ≥ K (LC 862) |
| Fixed-size window + max/min | Maximum of all subarrays of size k |
| Constrained optimization | Jump Game VI (LC 1696) |
Trigger phrases: "sliding window", "subarray of size k", "maximum/minimum in window"
Input: nums = [1,3,-1,-3,5,3,6,7], k = 3
Output: [3,3,5,5,6,7]
Use decreasing monotonic queue:
- Push new element (remove smaller elements from back)
- Pop expired elements from front (index out of window)
- Front is always the window maximum
For each bar, find: how far left/right can it extend?
= find previous smaller & next smaller elements
Use increasing monotonic stack for both passes.
-
Increasing vs Decreasing:
- Decreasing queue/stack → finding maximums or next greater
- Increasing queue/stack → finding minimums or next smaller
-
Both directions work: Left-to-right treats the stack as elements "waiting" for their answer. Right-to-left treats the stack as "candidates" to the right. Choose whichever mental model clicks for you.
-
Queue vs Stack choice:
- Need to expire old elements? → Queue (sliding window)
- Just need relationships? → Stack (next/previous element)
Interview tip: When you see O(n²) brute force for finding max/min or next greater/smaller, monotonic structures often give O(n). The insight is that dominated elements can be discarded—if a newer element is better, older worse elements will never be chosen.
Our implementation is a decreasing monotonic queue (maintains maximum at front). For a monotonic stack, simply use a regular stack and apply the same comparison logic—no special data structure needed.