Problem
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.
Sometimes k
is also denoted as w
.
Example
Example
Input: nums = [1,3,-1,-3,5,3,6,7], k = 3
Output: [3,3,5,5,6,7]
Explanation:
Window position Max
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
Solution
There are a couple of solutions possible for this problem, lets look at them.
Method 1 - Brute Force
An O(n*w
) bruteforce solution would be to do go through each of the element of the array and find minimum of w elements from the current position to the right.
Code
Java
public int[] maxSlidingWindow(int[] nums, int k) {
int j, max;
int n = nums.length;
int[] ans = new int[nums.length - k + 1];
for (int i = 0; i <= n - k; i++) {
max = Integer.MIN_VALUE;
for (j = 0; j < k; j++) {
max = Math.max(max, nums[j + i]);
}
ans[i] = max;
}
return ans;
}
Method 2 - Using Max Heap - but wrong approach ❌
This approach, even though looks legitimate will not work.
This will not work
If you closely observe the way we calculate the maximum in each w-sized subarray, you will notice that we’re doing repetitive work in the inner loop. Let’s understand it with an example:
Input: a[] = {1, 2, 3, 1, 4, 5, 2, 3, 6}, k = 3
For the above example, we first calculate the maximum in the subarray {1, 2, 3}
, then we move on to the next subarray {2, 3, 1}
and calculate the maximum. Notice that, there is an overlap in both the subarrays:
Subarray 1 - [1, 2, 3]
Subarray 2 - [2, 3, 1]
So we’re calculating the maximum in the overlapping part (2, 3
) twice. We can utilize the sliding window technique to save us from re-calculating the maximum in the overlapping subarray and improve the run-time complexity.
In the sliding window technique, we consider each k-sized subarray as a sliding window. To calculate the maximum in a particular window (2, 3, 1
), we utilize the calculation done for the previous window (1, 2, 3
).
Since we want to utilize the calculation from the previous window, we need a way to store the calculation for the previous window. We’ll use a PriorityQueue
to store the elements of the sliding window in decreasing order (maximum first).
Code
Java
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
int[] maxOfSubarrays = new int[n-k+1];
int idx = 0;
PriorityQueue<Integer> q = new PriorityQueue<>(Comparator.reverseOrder());
int start = 0;
int end = 0;
for(end = 0; end < n; end++) {
q.add(nums[end]);
if(end-start+1 == k) {
int max = q.peek();
maxOfSubarrays[idx++] = max;
if(max == nums[start]) {
q.remove();
}
start++;
}
}
return maxOfSubarrays;
}
The above code is buggy. For eg. Take nums = [9,10,9,-7,-4,-8,2,-6]
and k = 5
.
Heap = [9,10,9,-7,-4]
till i = k. When we now do peek, we get 10
.
i = 6, then 9 == 10, so we will just move forward and add -8. Notice, even though we have slided the window, we still have both 9
s in the heap: [9, 9, -7, -4, -8]
which is not good.
Method 3 - Using MaxHeap of Pair of Value and Index ✅
In method 2, we cannot figure out the way to remove values from heap, as it takes O(k) time.
What we can do is, to use a pair or [value, index]
instead of just inserting the value to queue. Whenever we remove the value as max element from queue, we make sure that it is not a stale index.
Code
Java
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
int[] ans = new int[n-k+1];
int idx = 0;
// maxHeap
PriorityQueue<Integer> q = new PriorityQueue<>(Comparator.reverseOrder());
int end = 0; // window end
for(end = 0; end < n; end++) {
//Pop the root (max_element),if it is out of sliding window
while(!q.isEmpty() && (end - q.top()[1]) >= k) {
q.pop();
}
q.add(new int[]{nums[end], end}); // add first w elements to queue
if(end >= k - 1) { // We've hit the window size. Find the maximum in the current window and Slide the window ahead
ans[idx++] = q.peek();
}
}
return ans;
}
Method 4 - Using balanced BST
Algorithm
- Construct a self-balancing binary search tree (BST) of size
w
with the firstw
elements of the input array. - Iterate through the remaining elements (
n - w
times):- Print the maximum element from the BST.
- Delete the current element (
arr[i]
) from the BST. - Insert the element
k
positions ahead (arr[i+k]
) into the BST.
Complexity
⏰ Time complexity:
O(n log w)
🧺 Space complexity:
O(w)
Initializing the BST (step 1) takes
O(w log w)
due to insertions in a self-balancing BST.Finding the maximum, deleting, and inserting elements (steps 2.1, 2.2, 2.3) each have
O(log w)
cost within the loop.Since the loop runs
n-w+1
times, the total complexity isO(w log w + (n-w+1) * log w)
, which simplifies toO(n log w)
.
Method 5 - Using DP
- Leveraging Element Scope: We recognize that elements can contribute to windows
w
positions to their left and right. (This remains the same) - Divide and Conquer with Blocks: We exploit this by dividing the array of size
n
into blocks of sizew
, so we getn/w
blocks. (This remains the same) - Calculating Maximums:
- Within each block, we calculate the maximum elements seen so far (
leftMax
) while traversing left to right. - We also calculate the maximum elements remaining (
rightMax
) while traversing right to left.
- Within each block, we calculate the maximum elements seen so far (
- Non-Overlapping Windows: Importantly, windows separated by elements less than or equal to
w
don’t overlap. (Change “greater than or equal to” to “less than or equal to”) - Resetting at Block Boundaries: We reset
leftMax
andrightMax
values at the boundaries of each block. - Finding Maximum at Each Position: Finally, the maximum at each position
i
in the current window (slidingMax[i]
) is the maximum ofrightMax[i]
and the correspondingleftMax[i + w - 1]
shifted byw-1
positions.
This approach achieves O(n) time complexity by efficiently calculating minimum elements within and across blocks.
Dry Run
For Example: A = [2, 1, 3, 4, 6, 3, 8, 9, 10, 12, 56]
, w=4
partition the array in blocks of size
w=4
. The last block may have less then w.2, 1, 3, 4 | 6, 3, 8, 9 | 10, 12, 56|
Traverse the list from start to end and calculate
maxSoFar
. Reset min to 0 after each block (ofw
elements).leftMax[] = 2, 2, 3, 4 | 6, 6, 8, 9 | 10, 12, 56
Similarly calculate
maxInFuture
by traversing from right to left.rightMax[] = 4, 4, 4, 4 | 9, 9, 9, 9 | 56, 56, 56
now, min at each position i in current window,
slidingMax[i] = min (rightMax[i], leftMax[i+w-1])
slidingMax = 4, 6, 6, 8, 9, 10, 12, ….
Code
Java
public int[] maxSlidingWindow(int[] nums, final int k) {
final int[] maxLeft = new int[nums.length];
final int[] maxRight = new int[nums.length];
maxLeft[0] = nums[0];
maxRight[nums.length - 1] = nums[nums.length - 1];
for (int i = 1; i < nums.length; i++) {
maxLeft[i] = (i % k == 0) ? nums[i] : Math.min(maxLeft[i - 1], nums[i]);
final int j = nums.length - i - 1;
maxRight[j] = (j % k == 0) ? nums[j] : Math.min(maxRight[j + 1], nums[j]);
}
final int[] ans = new int[nums.length - k + 1];
for (int i = 0, j = 0; i + k <= nums.length; i++) {
ans[j++] = Math.min(maxRight[i], maxLeft[i + k - 1]);
}
return ans;
}
Complexity
- ⏰ Time complexity:
O(n)
- 🧺 Space complexity:
O(n)
Method 6 - Using Monotonic Decreasing Deque (Save Even on space) 🏆
We can use deque datastructure.
Algorithm
- We iterate through the array, adding element indices (not values) to a deque.
- The deque maintains only the indices of the maximum elements within the current window of size
w
. - To create the initial window, we add the first
w
elements and remove any smaller indices encountered, ensuring the deque holds only the maximum element indices. - As we slide the window by one element:
- We remove the index falling outside the new window.
- We again remove any smaller indices from the deque to maintain the maximum element focus.
- The first element in the deque corresponds to the maximum element in the current window (due to the descending order based on element values).
Code
Java
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || nums.length == 0) {
return new int[0];
}
int n = nums.length;
int[] ans = new int[n - k + 1];
// In deque we insert element at Start i.e. first
// current max will be stored at last
Deque<Integer> deque = new LinkedList<Integer>();
for (int i = 0; i < n; i++) {
// remove the item from window once index is out
if (!deque.isEmpty() && deque.peekFirst() == i - k) {
deque.poll();
}
// if Current number is already higher than numbers in deque, just remove as this can be our sliding max
while (!deque.isEmpty() && nums[i] > nums[deque.peekLast()]) {
deque.removeLast();
}
deque.offer(i);
// as i + 1 is more than k, we can start filling in the result
if (i + 1 >= k) {
ans[i + 1 - k] = nums[deque.peek()];
}
}
return result;
}
Complexity
- ⏰ Time complexity:
O(n + w)
- 🧺 Space complexity:
O(w)
Dry Run
The maximum will always be the head of the queue.
int [] nums = { 11,12, 13, 12, 14, 11, 10, 9};
W = 3
Create deque for first w elements
int [] nums = { 11,12, 13, 12, 14, 11, 10, 9};
Deque: Output:
__________________________________
int [] nums = { 11, 12, 13, 12, 14, 11, 10, 9};
NOTE : we are adding the array index, not element
Deque: 0 Output:
__________________________________
int [] nums = { 11, 12, 13, 12, 14, 11, 10, 9};
12>11, remove 11 from deque and add 12’s index at the end
Deque: 1 Output:
__________________________________
int [] nums = { 11, 12, 13, 12, 14, 11, 10, 9};
13>12, remove 12 from deque and add 13’s index at the end
Deque: 2 Output: 13 (First index in deque)
At this point our first window with w elements is prepared. Now we will start discarding the elements from the deque which falls outside the window.
__________________________________
int [] nums = { 11, 12, 13, 12, 14, 11, 10, 9};
1. Indexes for new window 1-3 so remove indexes less than 1 from deque if present.
2. 12<13, add 12’s index at the end
Deque: 2 3 Output: 13 13
__________________________________
int [] nums = { 11, 12, 13, 12, 14, 11, 10, 9};
1. Indexes for new window 2-4 so remove indexes less than 2 from deque if present.
2. 14>12, remove 12 and 14>13, remove 13. Add 14 at the end
Deque: 4 Output: 13 13 14
__________________________________
int [] nums = { 11, 12, 13, 12, 14, 11, 10, 9};
1. Indexes for new window 3-5 so remove indexes less than 3 from deque if present.
2. 11< 14, Add 11 at the end
Deque: 4 5 Output: 13 13 14 14
__________________________________
int [] nums = { 11, 12, 13, 12, 14, 11, 10, 9};
1. Indexes for new window 4-6 so remove indexes less than 4 from deque if present.
2. 10< 11, Add 10 at the end
Deque: 4 5 6 Output: 13 13 14 14 14
__________________________________
int [] nums = { 11, 12, 13, 12, 14, 11, 10, 9};
1. Indexes for new window 4-7 so remove indexes less than 4 from deque if present. So 4 will be removed from deque.
2. Deque: 5 6
3. 9< 10, Add 9 at the end
Deque: 5 6 7 Output: 13 13 14 14 14 11
__________________________________