Sliding Window Maximum Explained
Asked 1 year ago
0Comments
6 Views
Understanding the Problem:
You're given an array of integers, nums, and a sliding window of size k that moves from the very left of the array to the very right. At each step, you can only see the k numbers within the window. The window slides one position to the right each time.
Your Task:
Return an array containing the maximum value in each sliding window.
didn't get it ? ! No Problem dosthh ! lets deep dive
Imagine you're driving along a straight road with gas stations (think of them as positions in the array). You have a window (of size k) that shows a fixed number of gas stations at a time. As you move forward (slide the window), you want to know the maximum gas available in your current view.
But instead of gas, we're dealing with numbers, and we want to find the maximum number within each window as it slides through the array.
Naive Approach: The Double Loop 🌀
Logic:
The simplest way to solve this problem is to:
- Iterate through the array with a loop.
- For each position, extract the current window of size
k. - Find the maximum in this window.
- Store the maximum in a result array.
- Repeat until the window has slid through the entire array.
Code Implementation:
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int> result;
int n = nums.size();
for(int i = 0; i <= n - k; ++i){
// Extract the window
vector<int> window(nums.begin() + i, nums.begin() + i + k);
// Find the maximum in the window
int max_val = *max_element(window.begin(), window.end());
// Store it
result.push_back(max_val);
}
return result;
}
};
Pros using this Approch ! :
- Simple to understand and implement.
Cons(dis-advantages) :
-
Inefficient for large datasets.
-
Time Complexity: O(n×k)O(n \times k)
For each of the n−k+1n - k + 1 windows, you're performing a kkk-length scan to find the maximum.
Why We Need a Better Approach:
Given the constraints (especially nums.length up to 10^5),
the naive approach becomes too slow. We need something more efficient—preferably O(n) time complexity.
Optimal Approach: Using a Deque 🕶️
Intuition:
To achieve O(n) time complexity, we need a way to:
- Find the maximum in each window efficiently.
- Avoid redundant comparisons.
A deque (double-ended queue) is perfect for this. Here's why:
- Maintain Order: We can keep track of potential maximum candidates in a way that the largest element is always at the front.
- Efficiently Add/Remove Elements: We can add or remove elements from both ends in constant time.
How It Works:
- Initialize a deque to store indices of elements. The deque will store indices in decreasing order of their corresponding values in
nums. - Iterate through the array:
- Remove indices from the back of the deque if the current element is greater than or equal to the elements corresponding to those indices. They can't be the maximum if the current element is larger.
- Add the current index to the back of the deque.
- Remove the front index if it's outside the current window (
i - k + 1). - The front of the deque now contains the index of the maximum element for the current window.
- Store the maximum for each window in the result array.
Visualization:
Let's walk through Example 1 to visualize the process.
Example 1:
- Input:
nums = [1,3,-1,-3,5,3,6,7],k = 3 - Output:
[3,3,5,5,6,7]
Step-by-Step Walkthrough:
| i | nums[i] | Deque Contents (Indices) | Current Window | Max in Window | Result |
|---|---|---|---|---|---|
| 0 | 1 | [0] | [1] | - | - |
| 1 | 3 | [1] | [1,3] | - | - |
| 2 | -1 | [1,2] | [1,3,-1] | 3 | [3] |
| 3 | -3 | [1,2,3] | [3,-1,-3] | 3 | [3,3] |
| 4 | 5 | [4] | [-3,5,3] | 5 | [3,3,5] |
| 5 | 3 | [4,5] | [5,3,6] | 5 | [3,3,5,5] |
| 6 | 6 | [6] | [3,6,7] | 6 | [3,3,5,5,6] |
| 7 | 7 | [7] | [6,7] | 7 | [3,3,5,5,6,7] |
Explanation:
-
Indices 0 to 2:
- Build the initial window
[1,3,-1]. - Deque maintains indices
[1,2]because3(index 1) is larger than1, and-1(index 2) is less than3. - Maximum is
3.
- Build the initial window
-
Index 3:
- Slide the window to
[3,-1,-3]. -3is added; deque becomes[1,2,3].- Maximum remains
3(index 1).
- Slide the window to
-
Index 4:
- Slide the window to
[-1,-3,5]. - Remove indices
1,2, and3as5is larger. - Deque becomes
[4]. - Maximum is
5.
- Slide the window to
-
Index 5:
- Slide the window to
[-3,5,3]. - Add index
5; deque becomes[4,5]. - Maximum remains
5(index 4).
- Slide the window to
-
Index 6:
- Slide the window to
[5,3,6]. - Remove index
5because6is larger. - Deque becomes
[4,6]. - Maximum is
6.
- Slide the window to
-
Index 7:
- Slide the window to
[3,6,7]. - Remove index
4because7is larger. - Deque becomes
[7]. - Maximum is
7.
- Slide the window to
Code Walkthrough:
#include <vector>
#include <deque>
using namespace std;
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
deque<int> dq; // Stores indices
vector<int> ans; // Stores the maximums
// Process the first 'k' elements
for (int i = 0; i < k; i++) {
// Remove elements from the back that
// are smaller than the current element
while (!dq.empty() && nums[i] >= nums[dq.back()]) {
dq.pop_back();
}
// Add current index to the back
dq.push_back(i);
}
// The front of the deque has the index of the
// largest element for the first window
ans.push_back(nums[dq.front()]);
// Process the remaining elements
for (int i = k; i < nums.size(); i++) {
// Remove the front element if it's outside the current window
if (!dq.empty() && i - dq.front() >= k) {
dq.pop_front();
}
// Remove elements from the back that are smaller
// than the current element
while (!dq.empty() && nums[i] >= nums[dq.back()]) {
dq.pop_back();
}
// Add current index to the back
dq.push_back(i);
// The front of the deque has the index of the
// largest element for the current window
ans.push_back(nums[dq.front()]);
}
return ans;
}
};
Didn't Understand ? Lets go Step-by-Step :
-
Initialize a Deque and Result Vector:
deque<int> dq;- Stores indices of potential maximum elements in the current window.
vector<int> ans;- Stores the maximums for each window.
-
Process the First Window (First 'k' Elements):
for (int i = 0; i < k; i++) {
while (!dq.empty() && nums[i] >= nums[dq.back()]) {
dq.pop_back();
}
dq.push_back(i);
}
- Purpose:
Populate the deque with indices from the first window while maintaining decreasing order of their correspondingnumsvalues. - How:
- Compare Current Element (
nums[i]) with the Last Element in Deque (nums[dq.back()]):
Ifnums[i]is greater or equal, pop the last index sincenums[i]could be a potential maximum for future windows. - Add Current Index to the Deque:
This ensures that the deque contains indices of elements in decreasing order.
- Compare Current Element (
3.Add Maximum of First Window to Result:
ans.push_back(nums[dq.front()]);
Why ! The Reason ! ? :
Because ! The front of the deque (dq.front()) holds the index of the maximum element for the first window.
3.Process the Remaining Elements:
for (int i = k; i < nums.size(); i++) {
// Remove indices outside the current window
if (!dq.empty() && i - dq.front() >= k) {
dq.pop_front();
}
// Remove elements smaller than the current element from the back
while (!dq.empty() && nums[i] >= nums[dq.back()]) {
dq.pop_back();
}
// Add current index to the deque
dq.push_back(i);
// Add the current maximum to the result
ans.push_back(nums[dq.front()]);
}
Steps Happening in this code block ! :
- Remove Out-of-Window Indices:
- If the index at the front of the deque is no longer within the current window, remove it.
- Maintain Decreasing Order in Deque:
- Remove all indices from the back whose corresponding
numsvalues are less than or equal tonums[i].
- Remove all indices from the back whose corresponding
- Add Current Index to Deque:
- Ensures the deque has potential candidates for the maximum.
- Add Current Maximum to Result:
- The front of the deque always holds the index of the maximum element for the current window.
---------------------------------------------------------------------------------------------------------------------------------------------
Why This Works:
-
Monotonic Deque:
The deque maintains indices in a monotonically decreasing order of their correspondingnumsvalues. This means:- The front always has the index of the current maximum.
- Any index whose corresponding value is less than the current element (
nums[i]) is removed because it cannot be the maximum in the presence of a larger element.
-
Efficiency:
- Each element is added and removed at most once, ensuring linear time complexity.
Time and Space Complexity:
- Time Complexity: O(n)
- Each element is processed exactly once.
- Space Complexity: O(k)
- The deque holds at most
kelements.
- The deque holds at most
Share