Facebook Pixel

239. Sliding Window Maximum

Problem Description

You have an array of integers called nums and a sliding window of size k that moves from left to right across the array. At each position, you can only see the k numbers currently inside the window. The window moves one position to the right at a time.

Your task is to find the maximum value in the window at each position and return all these maximum values as an array.

For example, if nums = [1, 3, -1, -3, 5, 3, 6, 7] and k = 3:

  • Window position [1, 3, -1] → maximum is 3
  • Window position [3, -1, -3] → maximum is 3
  • Window position [-1, -3, 5] → maximum is 5
  • Window position [-3, 5, 3] → maximum is 5
  • Window position [5, 3, 6] → maximum is 6
  • Window position [3, 6, 7] → maximum is 7

The result would be [3, 3, 5, 5, 6, 7].

The solution uses a max-heap (priority queue) to efficiently track the maximum value in each window. The heap stores tuples of (-value, index) where the negative value ensures the heap behaves as a max-heap. As the window slides, elements are added to the heap, and outdated elements (those outside the current window based on their index) are removed from the top of the heap. The maximum value for each window position is then extracted from the heap's top element.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The naive approach would be to check all k elements in each window position to find the maximum, giving us O(n*k) time complexity. We need something more efficient.

The key insight is that we want to quickly access the maximum element as the window slides. This naturally points us toward using a data structure that maintains elements in sorted order - a heap (priority queue).

However, there's a challenge: as the window slides, some elements become invalid (they fall outside the window). We can't just keep all elements in the heap forever. We need a way to identify and remove outdated elements.

This is where storing both the value and its index becomes crucial. By pairing each value with its original index (value, index), we can determine whether an element is still within the current window. If an element's index is less than or equal to i - k (where i is the current right boundary of the window), it's outside the window and should be discarded.

Since Python's heapq implements a min-heap by default, we use a clever trick: store negative values (-value, index) to simulate a max-heap. This way, the smallest negative number (which corresponds to the largest positive number) stays at the top.

The algorithm flow becomes clear:

  1. Pre-fill the heap with the first k-1 elements
  2. For each new position, add the new element to the heap
  3. Clean up outdated elements from the heap top
  4. The heap top now contains the maximum value for the current window

This approach reduces our time complexity to O(n log k) since we perform heap operations for each element, and heap operations take O(log k) time.

Solution Approach

The solution uses a max-heap implemented with Python's heapq module to efficiently track the maximum value in each sliding window.

Step 1: Initialize the heap

q = [(-v, i) for i, v in enumerate(nums[: k - 1])]
heapify(q)

We create a list of tuples containing (-value, index) for the first k-1 elements. The negative value trick converts Python's min-heap into a max-heap. We then heapify this list to create our initial heap structure.

Step 2: Process each window position

ans = []
for i in range(k - 1, len(nums)):

We start from index k-1 (where we have our first complete window of size k) and iterate through the rest of the array. The variable i represents the right boundary of our current window.

Step 3: Add new element to heap

heappush(q, (-nums[i], i))

For each position, we push the current element into the heap as a tuple (-nums[i], i). This ensures our heap always contains all potential maximum values.

Step 4: Remove outdated elements

while q[0][1] <= i - k:
    heappop(q)

Before extracting the maximum, we check if the top element of the heap is still within our current window. If q[0][1] <= i - k, it means the element at the heap's top is outside the current window (its index is too far to the left). We keep popping such elements until we find one that's within the window.

Step 5: Record the maximum

ans.append(-q[0][0])

After cleaning up outdated elements, the heap's top contains the maximum value for the current window. We negate it back to get the original positive value and add it to our result array.

The algorithm maintains the invariant that after cleanup, the heap's top always contains the maximum value within the current window of size k. The time complexity is O(n log k) where n is the array length, as each element is pushed and popped from the heap at most once, and heap operations take O(log k) time.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example with nums = [1, 3, -1, -3, 5] and k = 3.

Initial Setup:

  • First, we create a heap with the first k-1 = 2 elements
  • q = [(-1, 0), (-3, 1)] → After heapify: [(-3, 1), (-1, 0)]
  • The heap is organized as a min-heap, so (-3, 1) is at the top (representing value 3)

Window 1: [1, 3, -1] (i = 2)

  • Add (-(-1), 2) = (1, 2) to heap
  • Heap becomes: [(-3, 1), (-1, 0), (1, 2)]
  • Check top element: index 1 > 2 - 3 = -1, so it's valid (within window)
  • Maximum = -(-3) = 3
  • Result: [3]

Window 2: [3, -1, -3] (i = 3)

  • Add (-(-3), 3) = (3, 3) to heap
  • Heap becomes: [(-3, 1), (-1, 0), (1, 2), (3, 3)]
  • Check top element: index 1 > 3 - 3 = 0, so it's valid
  • Maximum = -(-3) = 3
  • Result: [3, 3]

Window 3: [-1, -3, 5] (i = 4)

  • Add (-5, 4) to heap
  • Heap becomes: [(-5, 4), (-3, 1), (1, 2), (3, 3), (-1, 0)]
  • Check top element: index 4 > 4 - 3 = 1, so it's valid
  • Maximum = -(-5) = 5
  • Result: [3, 3, 5]

The key insight is that we always maintain elements in the heap sorted by value (through the negative trick), and we lazily remove outdated elements only when they appear at the top. This ensures we always have quick access to the maximum valid element in O(log k) time.

Solution Implementation

1from typing import List
2from heapq import heapify, heappush, heappop
3
4class Solution:
5    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
6        # Initialize max heap with first k-1 elements
7        # Use negative values to simulate max heap (Python has min heap by default)
8        # Store tuples of (negative_value, index) to track element positions
9        max_heap = [(-value, index) for index, value in enumerate(nums[:k - 1])]
10        heapify(max_heap)
11      
12        # Result list to store maximum values of each window
13        result = []
14      
15        # Slide the window from position k-1 to the end
16        for i in range(k - 1, len(nums)):
17            # Add current element to the heap
18            heappush(max_heap, (-nums[i], i))
19          
20            # Remove elements that are outside the current window
21            # The window's valid range is [i - k + 1, i]
22            while max_heap[0][1] <= i - k:
23                heappop(max_heap)
24          
25            # The maximum element is at the top of the heap
26            # Convert back to positive value and add to result
27            result.append(-max_heap[0][0])
28      
29        return result
30
1class Solution {
2    /**
3     * Finds the maximum value in each sliding window of size k.
4     * Uses a max heap (priority queue) to efficiently track the maximum element.
5     * 
6     * @param nums the input array
7     * @param k the size of the sliding window
8     * @return an array containing the maximum value of each window
9     */
10    public int[] maxSlidingWindow(int[] nums, int k) {
11        // Max heap that stores [value, index] pairs
12        // Sorted by value in descending order, then by index in ascending order if values are equal
13        PriorityQueue<int[]> maxHeap = new PriorityQueue<>((a, b) -> {
14            if (a[0] == b[0]) {
15                return a[1] - b[1];  // If values are equal, sort by index ascending
16            }
17            return b[0] - a[0];  // Sort by value descending (max heap)
18        });
19      
20        int arrayLength = nums.length;
21      
22        // Add the first (k-1) elements to the heap
23        for (int i = 0; i < k - 1; i++) {
24            maxHeap.offer(new int[] {nums[i], i});
25        }
26      
27        // Result array will have (n - k + 1) windows
28        int[] result = new int[arrayLength - k + 1];
29        int resultIndex = 0;
30      
31        // Process each window starting from index (k-1)
32        for (int i = k - 1; i < arrayLength; i++) {
33            // Add current element to the heap
34            maxHeap.offer(new int[] {nums[i], i});
35          
36            // Remove elements that are outside the current window
37            // The window spans from (i - k + 1) to i
38            while (maxHeap.peek()[1] <= i - k) {
39                maxHeap.poll();
40            }
41          
42            // The maximum element in the current window is at the top of the heap
43            result[resultIndex++] = maxHeap.peek()[0];
44        }
45      
46        return result;
47    }
48}
49
1class Solution {
2public:
3    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
4        // Max heap to store pairs of (value, negative_index)
5        // Using negative index to maintain insertion order for equal values
6        priority_queue<pair<int, int>> maxHeap;
7      
8        int n = nums.size();
9      
10        // Initialize the heap with first k-1 elements
11        for (int i = 0; i < k - 1; ++i) {
12            maxHeap.push({nums[i], -i});
13        }
14      
15        vector<int> result;
16      
17        // Process each window starting from index k-1
18        for (int i = k - 1; i < n; ++i) {
19            // Add current element to the heap
20            maxHeap.push({nums[i], -i});
21          
22            // Remove elements that are outside the current window
23            // The window spans from [i-k+1, i]
24            while (-maxHeap.top().second <= i - k) {
25                maxHeap.pop();
26            }
27          
28            // The maximum element in current window is at the top of heap
29            result.emplace_back(maxHeap.top().first);
30        }
31      
32        return result;
33    }
34};
35
1function maxSlidingWindow(nums: number[], k: number): number[] {
2    // Max heap to store pairs of [value, negativeIndex]
3    // Using negative index to maintain insertion order for equal values
4    // TypeScript doesn't have a built-in priority queue, so we'll use an array
5    // and maintain it as a max heap manually
6    const maxHeap: [number, number][] = [];
7  
8    // Helper function to add element to max heap
9    const heapPush = (value: number, negativeIndex: number): void => {
10        maxHeap.push([value, negativeIndex]);
11        // Bubble up to maintain heap property
12        let currentIndex = maxHeap.length - 1;
13        while (currentIndex > 0) {
14            const parentIndex = Math.floor((currentIndex - 1) / 2);
15            // Compare by value first, then by negative index for stability
16            if (maxHeap[parentIndex][0] < maxHeap[currentIndex][0] ||
17                (maxHeap[parentIndex][0] === maxHeap[currentIndex][0] && 
18                 maxHeap[parentIndex][1] < maxHeap[currentIndex][1])) {
19                [maxHeap[parentIndex], maxHeap[currentIndex]] = 
20                [maxHeap[currentIndex], maxHeap[parentIndex]];
21                currentIndex = parentIndex;
22            } else {
23                break;
24            }
25        }
26    };
27  
28    // Helper function to remove top element from max heap
29    const heapPop = (): void => {
30        if (maxHeap.length === 0) return;
31      
32        maxHeap[0] = maxHeap[maxHeap.length - 1];
33        maxHeap.pop();
34      
35        if (maxHeap.length === 0) return;
36      
37        // Bubble down to maintain heap property
38        let currentIndex = 0;
39        while (true) {
40            let largestIndex = currentIndex;
41            const leftChild = 2 * currentIndex + 1;
42            const rightChild = 2 * currentIndex + 2;
43          
44            if (leftChild < maxHeap.length &&
45                (maxHeap[leftChild][0] > maxHeap[largestIndex][0] ||
46                 (maxHeap[leftChild][0] === maxHeap[largestIndex][0] && 
47                  maxHeap[leftChild][1] > maxHeap[largestIndex][1]))) {
48                largestIndex = leftChild;
49            }
50          
51            if (rightChild < maxHeap.length &&
52                (maxHeap[rightChild][0] > maxHeap[largestIndex][0] ||
53                 (maxHeap[rightChild][0] === maxHeap[largestIndex][0] && 
54                  maxHeap[rightChild][1] > maxHeap[largestIndex][1]))) {
55                largestIndex = rightChild;
56            }
57          
58            if (largestIndex !== currentIndex) {
59                [maxHeap[currentIndex], maxHeap[largestIndex]] = 
60                [maxHeap[largestIndex], maxHeap[currentIndex]];
61                currentIndex = largestIndex;
62            } else {
63                break;
64            }
65        }
66    };
67  
68    const n = nums.length;
69  
70    // Initialize the heap with first k-1 elements
71    for (let i = 0; i < k - 1; i++) {
72        heapPush(nums[i], -i);
73    }
74  
75    const result: number[] = [];
76  
77    // Process each window starting from index k-1
78    for (let i = k - 1; i < n; i++) {
79        // Add current element to the heap
80        heapPush(nums[i], -i);
81      
82        // Remove elements that are outside the current window
83        // The window spans from [i-k+1, i]
84        while (maxHeap.length > 0 && -maxHeap[0][1] <= i - k) {
85            heapPop();
86        }
87      
88        // The maximum element in current window is at the top of heap
89        result.push(maxHeap[0][0]);
90    }
91  
92    return result;
93}
94

Time and Space Complexity

Time Complexity: O(n × log k)

The algorithm uses a max heap to track the maximum element in each sliding window. Breaking down the operations:

  • Initial heap construction with k-1 elements takes O(k) time using heapify
  • The main loop runs n - k + 1 times (from index k-1 to n-1)
  • For each iteration:
    • heappush operation: O(log m) where m is the heap size
    • While loop for removing outdated elements: Each element is removed at most once throughout the entire execution
    • heappop operations: Amortized O(log m) per removed element

Although the heap might temporarily grow larger than k elements (before outdated elements are removed), each of the n elements is pushed exactly once and popped at most once. The heap operations dominate the complexity, with each push/pop taking O(log k) in the average case (since we maintain roughly k elements after cleanup).

Total: O(k) + O(n × log k) = O(n × log k)

Space Complexity: O(k)

The heap q stores elements from the current window and potentially some outdated elements that haven't been removed yet. In the worst case, the heap contains approximately k elements after the cleanup in each iteration. The answer list ans is not counted as auxiliary space since it's the required output.

Common Pitfalls

Pitfall 1: Not Properly Handling Elements Outside the Window

The Problem: A common mistake is to only check once if the top element is outside the window, rather than using a while loop. This can leave outdated elements at the top of the heap.

Incorrect Implementation:

# Wrong: Only checks once
if max_heap[0][1] <= i - k:
    heappop(max_heap)
result.append(-max_heap[0][0])

Why It Fails: Multiple consecutive elements at the top of the heap might be outside the current window. For example, if the heap contains [(-10, 0), (-9, 1), (-8, 5)] and the current window starts at index 4, both elements with indices 0 and 1 need to be removed.

Correct Solution:

# Correct: Uses while loop to remove ALL outdated elements
while max_heap[0][1] <= i - k:
    heappop(max_heap)
result.append(-max_heap[0][0])

Pitfall 2: Incorrect Window Boundary Calculation

The Problem: Misunderstanding the window boundaries leads to incorrect index comparisons when checking if an element is within the window.

Common Mistakes:

# Wrong: Off-by-one error
while max_heap[0][1] < i - k:  # Should be <=
    heappop(max_heap)

# Wrong: Incorrect window start calculation
while max_heap[0][1] < i - k + 1:  # Overly complex and error-prone
    heappop(max_heap)

Why It Matters: For a window ending at index i with size k, the valid indices are [i - k + 1, i]. An element at index j is outside the window if j <= i - k (equivalently, j < i - k + 1).

Correct Solution: Use the clearer condition max_heap[0][1] <= i - k to identify elements outside the window.

Pitfall 3: Forgetting to Store Index Information

The Problem: Storing only values in the heap without their indices makes it impossible to determine which elements are still within the current window.

Incorrect Implementation:

# Wrong: Only stores values
max_heap = [-v for v in nums[:k-1]]
heapify(max_heap)
for i in range(k-1, len(nums)):
    heappush(max_heap, -nums[i])
    # Cannot determine which elements to remove!
    result.append(-max_heap[0])

Correct Solution: Always store tuples of (negative_value, index) to track both the value and position of each element:

max_heap = [(-value, index) for index, value in enumerate(nums[:k - 1])]

Pitfall 4: Initializing with Full Window Instead of k-1 Elements

The Problem: Initializing the heap with all k elements before starting the main loop complicates the logic and can lead to off-by-one errors.

Problematic Approach:

# Confusing: Starting with full window
max_heap = [(-v, i) for i, v in enumerate(nums[:k])]
heapify(max_heap)
result = [-max_heap[0][0]]  # Need special handling for first window
for i in range(k, len(nums)):
    # Complex index management
    ...

Better Solution: Initialize with k-1 elements and let the main loop handle all windows uniformly:

max_heap = [(-v, i) for i, v in enumerate(nums[:k - 1])]
heapify(max_heap)
for i in range(k - 1, len(nums)):
    heappush(max_heap, (-nums[i], i))
    # Clean and consistent logic for all windows
    ...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which algorithm is best for finding the shortest distance between two points in an unweighted graph?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More