Double-Ended Queues and Monotonic Deque Invariants: Sliding Window Maximum
Harness monotonic double-ended queues (Deques) to solve continuous sliding window maximum and minimum problems in amortized O(n) linear time.
By the end, you’ll be able to…
- Formulate monotonic decreasing invariants inside double-ended queues
- Evict out-of-boundary and obsolete dominated elements to maintain strict O(1) amortized window querying
- Apply monotonic queue architectures to streaming sensor analytics and financial moving windows
01The Problem of Sliding Window Extremes
Given an array nums and a sliding window of size k moving from left to right, we must find the maximum value in every window. A naive solution scans all k elements for each of the (n - k + 1) window positions, resulting in O(n * k) runtime.
Using a PriorityQueue (Max-Heap) allows finding the maximum in O(1) and adding elements in O(log k). However, removing an element that leaves the window requires O(k) linear removal from the heap (or O(log k) with lazy deletion), resulting in O(n log k) overall time.
To achieve true amortized O(n) linear time, we utilize a Monotonic Double-Ended Queue (Deque). A monotonic deque stores elements in strictly descending order, guaranteeing that the maximum element of the current window is always at the front of the deque.
02The Domination Principle Explained
Why is it safe to discard smaller elements from the back of the deque? Consider two elements: nums[a] and nums[b], where a < b and nums[a] <= nums[b].
Because index b is newer than index a, nums[b] will remain in the sliding window longer than nums[a]. And because nums[b] is larger than or equal to nums[a], nums[a] can NEVER be the maximum in any current or future window that contains nums[b]!
Element nums[a] is completely dominated. Discarding it immediately maintains a strictly decreasing sequence in the deque, ensuring that peekFirst() always returns the window maximum.
03Step-by-Step Monotonic Deque Trace Table
Tracing nums = [1, 3, -1, -3, 5, 3, 6, 7] with k = 3:
+---+---------+-----------------------+-------------------+---------------+| i | nums[i] | Deque Indices (Values)| Action Taken | Result Output |+---+---------+-----------------------+-------------------+---------------+| 0 | 1 | [0 (1)] | Pushed 0 | - || 1 | 3 | [1 (3)] | 1 popped (1 < 3) | - || 2 | -1 | [1 (3), 2 (-1)] | Pushed 2 | 3 (Window 1) || 3 | -3 | [1 (3), 2 (-1), 3 (-3)| Pushed 3 | 3 (Window 2) || 4 | 5 | [4 (5)] | All popped (5 >) | 5 (Window 3) || 5 | 3 | [4 (5), 5 (3)] | Pushed 5 | 5 (Window 4) || 6 | 6 | [6 (6)] | All popped (6 >) | 6 (Window 5) || 7 | 7 | [7 (7)] | All popped (7 >) | 7 (Window 6) |+---+---------+-----------------------+-------------------+---------------+Final Output Array: [3, 3, 5, 5, 6, 7]04Amortized Complexity Analysis
Although the inner while loops appear to introduce nested iteration, each element index from 0 to n - 1 is pushed onto the deque exactly once and popped from the deque at most once.
Therefore, across the entire execution of the algorithm, at most 2n deque operations occur. The total amortized runtime is strictly O(n), and the auxiliary space is bounded by the window size O(k).
05Production Architecture & Engineering Trade-Offs
When selecting data structures for high-concurrency environments, developers must analyze both time complexity and memory overhead. Standard collection classes wrap primitive values in heap-allocated wrapper objects, introducing substantial pointer overhead and degrading CPU cache locality. In high-frequency messaging pipelines, storing millions of boxed integers in an ArrayList creates gigabytes of object overhead that stresses garbage collection cycles.
To achieve predictable low-latency throughput, production systems favor primitive-specialized collections or contiguous off-heap direct buffers. Additionally, when using ConcurrentHashMap, choosing the appropriate initial capacity and load factor avoids expensive table rehashing and concurrent bucket redistribution during peak transaction surges.
The ideas worth keeping.
- Monotonic Deques solve sliding window extreme problems in amortized O(n) linear time.
- Store array indices rather than raw values to enforce window boundary evictions cleanly.
- The domination principle proves that older, smaller elements can be discarded safely.
Make it yours.
Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.
Exercise 1: How would you modify the monotonic deque logi
How would you modify the monotonic deque logic to find the sliding window MINIMUM instead of maximum?
Explore a worked solution
// To find the sliding window minimum, maintain a monotonically INCREASING deque.// In step 2, remove elements from the back where nums[deque.peekLast()] > nums[i].// This ensures the smallest element of the window is always at the front (peekFirst()).Flip the comparison inequality: discard elements larger than the incoming element.
Exercise 2: Explain why storing indices rather than value
Explain why storing indices rather than values inside the Deque is essential for window boundary management.
Explore a worked solution
// Storing indices allows checking if the element at the front of the deque has expired:// if (deque.peekFirst() < i - k + 1) deque.pollFirst();// If only values were stored, we could not distinguish duplicate values or know// whether the front value fell outside the current sliding window boundary.How does the algorithm detect when an element has fallen outside the window?
What stayed with you?
Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.
Exercise 1: How would you modify the monotonic deque logi
How would you modify the monotonic deque logic to find the sliding window MINIMUM instead of maximum?
Build successful. (Java 15) [Process exited normally]
Keep exploring.
The official references behind this lesson, when you want a second angle or the full details.
One lesson closer.
Reading is a start. Trying it yourself makes the connection.