078. Sliding Window Maximum
Problem
Return the maximum of every contiguous window of exactly k integers. If k<1 or k exceeds input length, return []. Windows are reported in left-to-right order, and the input is unchanged.
Examples
Example 1
window_maximum([1,3,-1,-3,5,3,6,7], 3)
=> [3, 3, 5, 5, 6, 7]Expiration and domination maintain each window frontier.
Example 2
window_maximum([2,2,2], 2)
=> [2, 2]The newer equal value safely replaces the older one.
Example 3
window_maximum([], 1)
=> []No complete window exists.
Reveal the worked solution and reasoning
Why this mechanism works
A later value at least as large as an earlier candidate dominates it: the later value is better or equal and expires later. Keep only undominated indices in a deque with decreasing values. The front is the current maximum, while front expiration and back domination perform different jobs.
State and boundaries
candidates has increasing indices and strictly decreasing values. Every retained index belongs to the active window.
Protocol
- Expire indices at or before right-k.
- Remove back candidates no larger than the incoming value.
- Append the incoming index.
- Once k values have arrived, emit the front value.
Visualization
window [1,3,-1] -> deque values [3,-1]
new -3: [3,-1,-3]
new 5 dominates the back until deque [5]Python implementation
from collections import deque
def window_maximum(nums, k):
if k < 1 or k > len(nums):
return []
candidates = deque()
output = []
for right, value in enumerate(nums):
while candidates and candidates[0] <= right - k:
candidates.popleft()
while candidates and nums[candidates[-1]] <= value:
candidates.pop()
candidates.append(right)
if right + 1 >= k:
output.append(nums[candidates[0]])
return output
Worked execution
window_maximum([1,3,-1,-3,5,3,6,7], 3)
L4 window_maximum: if k < 1 or k > len(nums): nums=[1, 3, -1, -3, 5, 3, 6, 7]; k=3 L6 window_maximum: candidates = deque() nums=[1, 3, -1, -3, 5, 3, 6, 7]; k=3; candidates=deque([]) L7 window_maximum: output = [] nums=[1, 3, -1, -3, 5, 3, 6, 7]; k=3; candidates=deque([]); output=[] L8 window_maximum: for right, value in enumerate(nums): nums=[1, 3, -1, -3, 5, 3, 6, 7]; k=3; candidates=deque([]); output=[]; right=0; value=1 L13 window_maximum: candidates.append(right) nums=[1, 3, -1, -3, 5, 3, 6, 7]; k=3; candidates=deque([0]); output=[]; right=0; value=1 L8 window_maximum: for right, value in enumerate(nums): nums=[1, 3, -1, -3, 5, 3, 6, 7]; k=3; candidates=deque([0]); output=[]; right=1; value=3 L12 window_maximum: candidates.pop() nums=[1, 3, -1, -3, 5, 3, 6, 7]; k=3; candidates=deque([]); output=[]; right=1; value=3 L13 window_maximum: candidates.append(right) nums=[1, 3, -1, -3, 5, 3, 6, 7]; k=3; candidates=deque([1]); output=[]; right=1; value=3 ... 20 intermediate state changes omitted ... L8 window_maximum: for right, value in enumerate(nums): nums=[1, 3, -1, -3, 5, 3, 6, 7]; k=3; candidates=deque([6]); output=[3, 3, 5, 5, 6]; right=7; value=7 L12 window_maximum: candidates.pop() nums=[1, 3, -1, -3, 5, 3, 6, 7]; k=3; candidates=deque([]); output=[3, 3, 5, 5, 6]; right=7; value=7 L13 window_maximum: candidates.append(right) nums=[1, 3, -1, -3, 5, 3, 6, 7]; k=3; candidates=deque([7]); output=[3, 3, 5, 5, 6]; right=7; value=7 L15 window_maximum: output.append(nums[candidates[0]]) nums=[1, 3, -1, -3, 5, 3, 6, 7]; k=3; candidates=deque([7]); output=[3, 3, 5, 5, 6, 7]; right=7; value=7
Result: [3, 3, 5, 5, 6, 7]. Expiration and domination maintain each window frontier.
window_maximum([2,2,2], 2)
L4 window_maximum: if k < 1 or k > len(nums): nums=[2, 2, 2]; k=2 L6 window_maximum: candidates = deque() nums=[2, 2, 2]; k=2; candidates=deque([]) L7 window_maximum: output = [] nums=[2, 2, 2]; k=2; candidates=deque([]); output=[] L8 window_maximum: for right, value in enumerate(nums): nums=[2, 2, 2]; k=2; candidates=deque([]); output=[]; right=0; value=2 L13 window_maximum: candidates.append(right) nums=[2, 2, 2]; k=2; candidates=deque([0]); output=[]; right=0; value=2 L8 window_maximum: for right, value in enumerate(nums): nums=[2, 2, 2]; k=2; candidates=deque([0]); output=[]; right=1; value=2 L12 window_maximum: candidates.pop() nums=[2, 2, 2]; k=2; candidates=deque([]); output=[]; right=1; value=2 L13 window_maximum: candidates.append(right) nums=[2, 2, 2]; k=2; candidates=deque([1]); output=[]; right=1; value=2 ... 1 intermediate state changes omitted ... L8 window_maximum: for right, value in enumerate(nums): nums=[2, 2, 2]; k=2; candidates=deque([1]); output=[2]; right=2; value=2 L12 window_maximum: candidates.pop() nums=[2, 2, 2]; k=2; candidates=deque([]); output=[2]; right=2; value=2 L13 window_maximum: candidates.append(right) nums=[2, 2, 2]; k=2; candidates=deque([2]); output=[2]; right=2; value=2 L15 window_maximum: output.append(nums[candidates[0]]) nums=[2, 2, 2]; k=2; candidates=deque([2]); output=[2, 2]; right=2; value=2
Result: [2, 2]. The newer equal value safely replaces the older one.
Why the solution is correct
Removing an expired index is mandatory. Removing a dominated candidate is safe because its later replacement remains available whenever the older one would be relevant. The largest retained value stays at the front and represents the active window maximum.
Time and space
O(n) time and O(k) auxiliary deque space, plus output.
Common mistakes
Storing values alone makes expiration ambiguous with duplicates. A heap without removing stale entries can report an out-of-window maximum.
Reasoning card
State: candidates has increasing indices and strictly decreasing values. Every retained index belongs to the active window.
Invariant: Deque indices are in-window and values decrease front to back.
Decision to rehearse: Expire out-of-window indices; remove dominated values from the back.
Finish: Once k values have arrived, emit the front value
Transfer exercise
Maintain both minimum and maximum deques to find the longest window whose range is at most a limit.