The Hard Problem: Sliding Window Max
[!NOTE] This module explores the core principles of the Sliding Window Maximum problem, deriving solutions from first principles and hardware constraints to build world-class, production-ready expertise.
1. Problem Definition
Given an array 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.
Example:
Given nums = [1, 3, -1, -3, 5, 3, 6, 7] and k = 3.
- Window 1
[1, 3, -1]: Max = 3 - Window 2
[3, -1, -3]: Max = 3 - Window 3
[-1, -3, 5]: Max = 5
Edge Cases & Clarifying Questions
- Empty array (
nums.length == 0): What should be returned? (Return an empty array). k= 1: The sliding window size is 1, so the maximum of each window is the element itself. Return the original array.- Negative Numbers: The array can contain negative numbers. Ensure that the initial maximum isn’t incorrectly set to 0.
k>nums.length: Oftenkis constrained to1 <= k <= nums.length, but ifkexceeds the array size, return the maximum of the entire array.
2. Interactive Analysis
We need a structure that gives us the max in O(1). A Deque (Double-Ended Queue) stores indices of “promising” candidates.
Rules:
- Remove Old: If the index at the front is out of the window (
i - k), pop front. - Maintain Order: Before pushing current
x, pop everything from the back that is smaller thanx. (Why keep a small number if a newer, larger number exists?) - Result: The front of the deque is always the max for the current window.
Interactive: Deque Visualizer
Watch how candidates are added and removed.
Array (Window K=3)
Deque (Indices)
Result
3. Solutions
Intuition (The “Genesis”)
Brute Force Approach:
The most straightforward way is to iterate through each window of size k and find the maximum element. Since there are N - k + 1 windows and finding the maximum in each window takes O(k) time, the overall time complexity is O(N * K). This is too slow for large inputs.
Optimized Approach (Monotonic Deque):
We want to find the maximum in O(1) time for each window, leading to an O(N) overall time complexity.
The key insight is that if a number x is larger than all currently stored numbers y that came before it, then those y numbers can never be the maximum of the window again. Why? Because x is larger AND x will stay in the window longer than y. So we can safely discard y.
This property (maintaining elements in decreasing order) explicitly names the pattern: the Monotonic Deque (Double-Ended Queue). We store the indices of elements in the deque, ensuring the values they point to are strictly monotonically decreasing.
Common Pitfalls
- Storing Values instead of Indices: If you store values in the deque, you lose the ability to check if the maximum element (at the front of the deque) has fallen out of the sliding window. Always store indices.
- Not Popping Smaller Elements First: When adding a new element, you must pop all smaller elements from the back of the deque before pushing the new element. Failing to do this breaks the monotonic property.
- Handling Out-of-Bounds Elements: Forgetting to remove elements from the front of the deque when their indices are outside the current window (
i - k).
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || k <= 0) return new int[0];
int n = nums.length;
int[] res = new int[n - k + 1];
int ri = 0; // result index
Deque<Integer> q = new ArrayDeque<>(); // Stores indices
for (int i = 0; i < n; i++) {
// 1. Remove out of bounds
while (!q.isEmpty() && q.peek() < i - k + 1) {
q.poll();
}
// 2. Remove smaller elements from back
while (!q.isEmpty() && nums[q.peekLast()] < nums[i]) {
q.pollLast();
}
q.offer(i);
if (i >= k - 1) {
res[ri++] = nums[q.peek()];
}
}
return res;
}
func maxSlidingWindow(nums []int, k int) []int {
if len(nums) == 0 { return []int{} }
var res []int
var q []int // Indices
for i, n := range nums {
// 1. Remove out of bounds
if len(q) > 0 && q[0] < i-k+1 {
q = q[1:]
}
// 2. Remove smaller
for len(q) > 0 && nums[q[len(q)-1]] < n {
q = q[:len(q)-1]
}
q = append(q, i)
if i >= k-1 {
res = append(res, nums[q[0]])
}
}
return res;
}
4. Complexity Analysis
| Strategy | Time | Space | Notes |
|---|---|---|---|
| Naive | O(N * K) | O(1) | Repeatedly scanning the window. |
| Monotonic Deque | O(N) | O(K) | Each element is pushed and popped at most once. |