Problem overview
The method maxSlidingWindow receives an int[] nums and a window size k. A window of k neighbouring values starts at the left end and slides right one position at a time until it reaches the right end. The method returns an int[] with the largest value of every window, from left to right. There are nums.length - k + 1 windows, so that is the length of the result.
For nums = [1, 3, -1, -3, 5, 3, 6, 7] and k = 3, the windows are [1, 3, -1], [3, -1, -3], [-1, -3, 5], [-3, 5, 3], [5, 3, 6] and [3, 6, 7]. Their maxima are [3, 3, 5, 5, 6, 7]. For [7, 2, 4] and k = 2 the answer is [7, 4]: once 7 has left the window, 4 is the largest value.
Approach: keep only the values that can still win
Scanning each window for its maximum takes O(n · k) time in total. With 100,000 values and a large k that is too slow. The lesson asks for O(n) time and O(n) extra space.
The key observation: when a new value nums[i] enters the window, every earlier value in the window that is not bigger than it can never be a maximum again. It leaves the window before nums[i] does, and while it is inside, nums[i] is there too and at least as big. So we can throw such values away immediately.
What remains are the candidates: the values that can still become a maximum. They are kept in a Deque (a queue that can be read and changed at both ends) as indexes, in increasing index order, and their values are strictly decreasing from front to back. That has three consequences:
- The front holds the maximum of the current window.
- The oldest candidate is at the front, so when an index slides out of the window, it is the front that leaves.
- Storing indexes instead of values lets you tell whether the front is still inside the window.
Here is the deque on the first example, as indexes and as the values they point to:
i=0 value=1 indexes=[0] values=[1]
i=1 value=3 indexes=[1] values=[3]
i=2 value=-1 indexes=[1, 2] values=[3, -1] max=3
i=3 value=-3 indexes=[1, 2, 3] values=[3, -1, -3] max=3
i=4 value=5 indexes=[4] values=[5] max=5
i=5 value=3 indexes=[4, 5] values=[5, 3] max=5
i=6 value=6 indexes=[6] values=[6] max=6
i=7 value=7 indexes=[7] values=[7] max=7
At i = 1 the value 3 removes the 1 before it. At i = 4 the value 5 removes everything. At i = 5 the 3 stays behind the 5, because it may be the maximum once the 5 has left.
Each index enters the deque once and leaves it at most once, so all the work in the loop adds up to O(n), however big k is.
Step by step
- Create the deque
candidates, aDeque<Integer>built as a newArrayDeque<>(). Both ends work inO(1):peekFirstandpollFirstread and remove the front,peekLastandpollLastthe back, andaddLastadds at the back. - Create the result array with
nums.length - k + 1slots, one per window. - Loop over every index
i. Each turn,iis the window's right end: the window is thekvalues that end ati. - Drop the front if it left the window. The window now starts at
i - k + 1, so an index ofi - kor less is outside. Only one index can leave per turn, so one check is enough. - Drop the back while it can never win again: while the deque isn't empty and
nums[candidates.peekLast()]is<= nums[i]. - Remove the back with
pollLast()inside that loop. - Add
iat the back. Nownums[i]is smaller than everything left in the deque, so the values still decrease from front to back. - Record the maximum once the window is full, that is, when
i >= k - 1:nums[candidates.peekFirst()]goes intoresult[i - k + 1], the window's own place in the result. - Return
result.
The solution in Java
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
Deque<Integer> candidates = new ArrayDeque<>();
int[] result = new int[nums.length - k + 1];
for (int i = 0; i < nums.length; i++) {
if (!candidates.isEmpty() && candidates.peekFirst() <= i - k) candidates.pollFirst();
while (!candidates.isEmpty() && nums[candidates.peekLast()] <= nums[i]) {
candidates.pollLast();
}
candidates.addLast(i);
if (i >= k - 1) result[i - k + 1] = nums[candidates.peekFirst()];
}
return result;
}
}
Java details:
Dequeis an interface,ArrayDequea class. Declaring the variable asDeque<Integer>keeps the code to the methods you need.ArrayDequeis a resizable array used as a deque, and it is the usual choice for this kind of work.- Autoboxing. The deque holds
Integerobjects, butaddLast(i)accepts anintandnums[candidates.peekLast()]unboxes the index to use it as an array subscript. Comparing with<=unboxes too. Compare twoIntegerobjects withequals, never with==. peekandpollreturnnullon an empty deque, wheregetFirstandremoveFirstwould throw. TheisEmpty()checks come first, because unboxing anullinto anintwould throw aNullPointerException.- The order of the conditions.
&&stops at the firstfalse, sopeekFirst()andpeekLast()are only read when the deque isn't empty.
Complexity
Time: O(n). The loop has n turns. The inner while can remove several indexes in one turn, but every index is added once and removed at most once, so across the whole run it removes at most n in total.
Space: O(n) for the result and O(k) for the deque. The deque can hold at most k indexes at a time, because older ones are dropped from the front. The result has n - k + 1 entries.
Edge cases and common mistakes
k = 1. Every window is a single value, so the result equalsnums.[1]withk = 1gives[1].k = nums.length. There is one window.[9, 8, 7]withk = 3gives[9].- Equal values.
[4, 4, 4, 4]withk = 2gives[4, 4, 4]. The<=in thewhiledrops an equal older value, which is fine: the newer copy stays in the window longer. A strict<would also give correct maxima, but would keep more equal values in the deque. - A decreasing array. Nothing is ever removed from the back, and the front leaves one index at a time. For
[5, 4, 3, 2, 1]withk = 3the answer is[5, 4, 3]. - Storing values instead of indexes. Then you can't tell when the front has left the window.
- Off-by-one in the window start. The window that ends at
istarts ati - k + 1, so an index ofi - kis already out. The check must therefore includei - kitself, as<=does. - Result size. It is
nums.length - k + 1, notnums.length.
Testing
nums = [1, 3, -1, -3, 5, 3, 6, 7],k = 3gives[3, 3, 5, 5, 6, 7].nums = [7, 2, 4],k = 2gives[7, 4].nums = [1, 2, 3, 4, 5],k = 3gives[3, 4, 5].nums = [5, 4, 3, 2, 1],k = 3gives[5, 4, 3].nums = [4, 4, 4, 4],k = 2gives[4, 4, 4].
