1. Problem Statement
The Sliding Window Maximum problem asks us to find the maximum element in every contiguous subarray (window) of a fixed size k within a given array nums.
As the window slides from the extreme left of the array to the extreme right by one position at a time, you can only see k numbers in the window. Your goal is to return an array containing the maximum value of each window state.
Key constraints to consider:
- You are given an array of integers
nums. - You are given an integer
k representing the window size. - 1≤k≤nums.length.
2. Examples
Example 1
Input: nums = [1, 3, -1, -3, 5, 3, 6, 7], k = 3 Output: [3, 3, 5, 5, 6, 7]
| Window Position |
Window Elements |
Maximum |
| [1 3 -1] -3 5 3 6 7 |
1, 3, -1 |
3 |
| 1 [3 -1 -3] 5 3 6 7 |
3, -1, -3 |
3 |
| 1 3 [-1 -3 5] 3 6 7 |
-1, -3, 5 |
5 |
| 1 3 -1 [-3 5 3] 6 7 |
-3, 5, 3 |
5 |
| 1 3 -1 -3 [5 3 6] 7 |
5, 3, 6 |
6 |
| 1 3 -1 -3 5 [3 6 7] |
3, 6, 7 |
7 |
Example 2
Input: nums = [1, -1], k = 1 Output: [1, -1] Explanation: When window size is 1, the maximum of each window is simply the element itself.
3. Brute Force Approach
Intuition
The most straightforward way is to simulate the sliding window exactly as described. For every possible starting position of the window, we iterate through the k elements currently in that window and find the maximum.
Data Structures Used
std::vector<int> to store the result.
Algorithm
- Create a
result array. - Loop
i from 0 to n - k (the starting index of each window). - Initialize a variable
max_val to the smallest possible integer. - Loop
j from i to i + k - 1 (the elements inside the current window). - Update
max_val = max(max_val, nums[j]). - Append
max_val to result. - Return
result.
Step-by-step Dry Run
Input: nums = [1, 3, -1], k = 2
i = 0: Window is nums[0..1] -> [1, 3]. Inner loop finds max is 3. Result: [3].i = 1: Window is nums[1..2] -> [3, -1]. Inner loop finds max is 3. Result: [3, 3].
C++ Implementation
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;
class SolutionBrute {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int n = nums.size();
vector<int> result;
for (int i = 0; i <= n - k; i++) {
int max_val = INT_MIN;
for (int j = i; j < i + k; j++) {
max_val = max(max_val, nums[j]);
}
result.push_back(max_val);
}
return result;
}
};
Optimal Approach
The standard O(N) sliding window maximum utilizes a Monotonic Decreasing Deque.
Core Intuition
Imagine a line of people sorted by height. If a taller person joins the line, anyone shorter than them who is ahead of them in the line becomes useless because they can never be the tallest in the group (the new taller person will outlast them as the window moves). We can maintain a queue where elements are strictly decreasing.
Why Double Ended Queue (Deque) is used
We need to remove elements from the back (when a larger element arrives, rendering smaller ones useless) and from the front (when the maximum element slides out of the window boundary). A std::deque supports O(1) insertions and deletions at both ends.
Why we store Indices instead of Values
Storing indices allows us to easily check if the element at the front of the deque has fallen out of the current window (index <= i - k). We can always get the value using nums[index].
Explain every data structure before coding
std::deque<int> dq: Stores indices of array elements. The elements corresponding to these indices are kept in strictly decreasing order.std::vector<int> result: Stores the maximum values for each window.
6. Algorithm
- Initialize
deque<int> dq and vector<int> result. - Iterate
i through the array from 0 to N-1. - Remove Out-of-Bounds: Check if the index at the front of the deque is ≤i−k. If so,
dq.pop_front() because it is no longer in the window. - Maintain Monotonic Property: While the deque is not empty AND the current element
nums[i] is ≥ the element at the back of the deque nums[dq.back()], dq.pop_back(). (Smaller elements are useless now). - Add Current: Push the current index
i to the back of the deque. - Record Result: If the window has hit size
k (which happens when i >= k - 1), push nums[dq.front()] to the result array.
7. Complete C++ Code
#include <vector>
#include <deque>
using namespace std;
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int> result;
deque<int> dq;
for (int i = 0; i < nums.size(); i++) {
if (!dq.empty() && dq.front() == i - k) {
dq.pop_front();
}
while (!dq.empty() && nums[dq.back()] <= nums[i]) {
dq.pop_back();
}
dq.push_back(i);
if (i >= k - 1) {
result.push_back(nums[dq.front()]);
}
}
return result;
}
};
8. Dry Run
Take the following example: nums = [1, 3, -1, -3, 5, 3], capacity (k) = 3
| Step (i) |
Value |
Deque (Indices) |
Deque (Values) |
Out of Bounds Removed? |
Smaller Elements Popped? |
Result Added |
| i = 0 |
1 |
[0] |
[1] |
No |
No |
Not yet |
| i = 1 |
3 |
[1] |
[3] |
No |
Yes (1 ≤ 3) |
Not yet |
| i = 2 |
-1 |
[1, 2] |
[3, -1] |
No |
No |
3 (front) |
| i = 3 |
-3 |
[1, 2, 3] |
[3, -1, -3] |
No (front is 1, window is 1–3) |
No |
3 (front) |
| i = 4 |
5 |
[4] |
[5] |
Yes (front 1 was removed) |
Yes (-3, -1, 3 popped) |
5 (front) |
| i = 5 |
3 |
[4, 5] |
[5, 3] |
No |
No |
5 (front) |
Final Result: [3, 3, 5, 5]
Final Result: [3, 3, 5, 5]
9. Complexity Analysis
| Operation |
Time Complexity |
Space Complexity |
| maxSlidingWindow() |
O(N) |
O(K) auxiliary space |
Why it is O(N) Time:
You might see a while loop inside a for loop and think O(N2). However, look closer at the deque operations. Every index is pushed into the deque exactly once, and popped from the deque at most once. Therefore, the while loop runs at most N times across the entire execution of the program. Amortized, it is O(1) per element, making the total time O(N).
Why it is O(K) Space:
The deque stores indices. Because we constantly pop elements that are out of bounds or strictly smaller, the deque will contain at most K elements at any given time (this worst case happens when the array is strictly decreasing).
10. Visualization
Let's visualize the deque maintaining the monotonic property for window: [3, -1, 5] with k=3.
11. Interview Tips
- Common mistakes: Pushing values to the deque instead of indices. If you push values, you have no efficient way to know if an element has fallen out of the left side of your window.
- Edge cases: K=1. The algorithm handles it smoothly, but some candidates try to write separate logic for it. Trust the deque logic.
- Why Heap is sub-optimal: A heap takes O(logN) to process elements and doesn't easily allow removing arbitrary elements when they leave the window unless doing "lazy deletion" which balloons the space to O(N).
- "Useless Elements" Concept: When explaining to an interviewer, heavily emphasize the phrase: "If a new element is larger than previous ones in the window, those previous ones can never be the maximum again. They are useless." This proves you understand the monotonic logic.
video link:
code link:
https://leetcode.com/problems/sliding-window-maximum/description/