Lesson 17 of 47 3 min

MANG Problem #6: Sliding Window Maximum (Hard)

Learn how to find the maximum in every sliding window of size K in O(n) time using a Monotonic Deque.

1. Problem Statement

You are given an array of integers nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position.

Return the max sliding window.

Input: nums = [1,3,-1,-3,5,3,6,7], k = 3
Output: [3,3,5,5,6,7]

2. Approach: Monotonic Deque (O(n))

graph LR
    subgraph "Monotonic Deque (Indices)"
        D[Front: Max] --- D1[Index 1] --- D2[Index 2] --- D3[Back: Smallest]
    end
    
    Window[Sliding Window] --> Deque
    New[New Element] --> Check{Larger than Back?}
    Check -- Yes --> PopBack[Pop Back] --> New
    Check -- No --> PushBack[Push Back]

The Naive Way (O(N*K))

For every window position, iterate through all $K$ elements to find the max.

  • Why it fails: When $N=10^5$ and $K=10^4$, we do $10^9$ operations.

The Optimized Way: Monotonic Deque

We need a way to find the max in $O(1)$ and update it in $O(1)$. We use a Deque (Double-ended queue) to store indices of elements. We maintain the property that the elements at these indices are in strictly decreasing order.

  1. Pop from Back: Before adding a new element, remove all indices from the back whose values are smaller than the current element (they will never be the max again).
  2. Add to Back: Add the current index.
  3. Pop from Front: If the index at the front is outside the window range, remove it.
  4. Result: The element at deque.peekFirst() is always the maximum for the current window.

3. Java Implementation

public int[] maxSlidingWindow(int[] nums, int k) {
    if (nums == null || k <= 0) return new int[0];
    int n = nums.length;
    int[] result = new int[n - k + 1];
    int ri = 0;
    
    // Store indices
    Deque<Integer> q = new ArrayDeque<>();
    
    for (int i = 0; i < nums.length; i++) {
        // 1. Remove indices of elements smaller than current from back
        while (!q.isEmpty() && nums[q.peekLast()] < nums[i]) {
            q.pollLast();
        }
        
        // 2. Add current index
        q.offer(i);
        
        // 3. Remove index if it's outside the window
        if (q.peekFirst() == i - k) {
            q.pollFirst();
        }
        
        // 4. If window is full, add front to result
        if (i >= k - 1) {
            result[ri++] = nums[q.peekFirst()];
        }
    }
    return result;
}

4. 5-Minute "Video-Style" Walkthrough

  1. The "Aha!" Moment: If you have a new number that is larger than the existing numbers in the window, those smaller numbers are useless. They will never be the maximum because the new larger number will stay in the window longer than they will.
  2. The Deque Strategy: By removing smaller elements from the back, we ensure the "King" (the largest) is always at the front.
  3. The Cleanup: As the window moves, the "King" might get too old. We check the index at the front; if it's older than i - k, we "dethrone" it.

5. Interview Discussion

  • Interviewer: "Why store indices instead of values?"
  • You: "Indices allow us to easily check if the element has fallen out of the window's range."
  • Interviewer: "Can you use a Priority Queue?"
  • You: "Yes, but that would be $O(N \log N)$ because removing an arbitrary element from a heap takes $O(K)$. The Monotonic Deque gives us the optimal $O(N)$ solution."

Want to track your progress?

Sign in to save your progress, track completed lessons, and pick up where you left off.