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
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
|
|
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
|
|
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
|
|
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
|
|
Complexity
- ⏰ Time complexity:
O(n + w)
- 🧺 Space complexity:
O(w)
Dry Run
The maximum will always be the head of the queue.
|
|