Shortest Subarray with Sum at Least K looks like a sliding-window problem, but the array can contain negative numbers — and that one detail breaks the simple window. The fix combines two classic tools: prefix sums and a monotonic deque.
Problem. Given an integer array nums (which may contain negatives) and an integer K, return the
length of the shortest non-empty subarray whose sum is at least K. If no such subarray exists,
return -1.
Example: nums = [2, -1, 2], K = 3 → answer 3 (the whole array sums to 3; no shorter window reaches 3).
The slow way first
The obvious idea: try every subarray. For each left end, extend right and track the running sum, stopping when it reaches K. That is O(n²) and far too slow for large inputs.
The question to ask: can I express a subarray sum so two endpoints are independent? Yes — with prefix sums. Let prefix[i] be the sum of the first i numbers. Then the sum of nums[l..r-1] is just prefix[r] - prefix[l]. We want the shortest gap r - l where prefix[r] - prefix[l] >= K.
The idea: an increasing deque of prefix indices
Sweep i over the prefix array, keeping a deque of candidate left ends with two rules:
- Pop the front while
prefix[i] - prefix[front] >= K. That front gives a valid window, and since we sweep left to right, this is the shortest window ending here for that front — record it and discard the front (a lateriwould only be longer). - Pop the back while
prefix[back] >= prefix[i]. A later index with a smaller-or-equal prefix is always a better left end, so the old back is useless.
These two rules keep the deque strictly increasing in prefix value, and every index enters and leaves it at most once.
The key insight: a negative number can shrink a prefix, so a later index can be a strictly better left end than an earlier one — which is exactly why we maintain an increasing deque instead of a plain window.
Walk through it
Step through the animation. For nums = [2, -1, 2] the prefix array is [0, 2, 1, 3]. Watch index 1 (prefix 2) get popped from the back when index 2 (prefix 1) arrives, and index 0 get popped from the front when index 3 (prefix 3) reaches a difference of 3 >= K.
Pseudocode
build prefix where prefix[i] = sum of first i numbers
best = infinity
deque dq = empty # holds indices, increasing prefix values
for i from 0 to len(prefix) - 1:
while dq and prefix[i] - prefix[dq.front] >= K:
best = min(best, i - dq.pop_front())
while dq and prefix[dq.back] >= prefix[i]:
dq.pop_back()
dq.push_back(i)
return best if best is finite else -1The Python solution
def shortest_subarray(nums, K):
prefix = [0]
for x in nums:
prefix.append(prefix[-1] + x)
best, dq = len(nums) + 1, deque()
for i, cur in enumerate(prefix):
while dq and cur - prefix[dq[0]] >= K:
best = min(best, i - dq.popleft())
while dq and prefix[dq[-1]] >= cur:
dq.pop()
dq.append(i)
return best if best <= len(nums) else -1prefixis the running-sum array;prefix[i] - prefix[l]is the sum ofnums[l..i-1].dqholds indices intoprefix, kept increasing by prefix value.- Line 7 is the front drain: every front whose window reaches
Kyields a candidate lengthi - front, and we pop it because no later right end gives a shorter window for that front. - Line 9 is the back drain: an index with prefix
>= curcan never be a better left end thani, so we remove it. - We append
ilast, so it becomes available as a left end for future right ends.
Complexity
| Case | Time | Notes |
|---|---|---|
| Brute force (every subarray) | O(n²) (slow) | two nested loops |
| Prefix + monotonic deque | O(n) (moderate) | each index pushed and popped once |
O(n) (moderate)We trade O(n) extra space (the prefix array and deque) for a one-pass solution. The amortized argument is the heart of every monotonic-deque problem: each index is added once and removed once, so the inner while loops are O(n) in total, not O(n²).
When this pattern shows up
When a windowed problem has negative numbers, the plain sliding window breaks because growing the window no longer monotonically grows the sum. Reach for prefix sums + a monotonic deque: the deque of increasing prefixes lets a later, smaller prefix replace an earlier candidate left end.
The deque holds indices, not values, and stays increasing in prefix value. Drain the front first (record answers), then the back (maintain the invariant), then push. Swapping that order silently drops valid windows.
Practice
For nums = [2, -1, 2], the prefix array is [0, 2, 1, 3]. When i = 2 (prefix 1) arrives, what happens to index 1 (prefix 2) at the back of the deque?
1. Why does a plain sliding window fail on this problem?
2. What does the deque store, and in what order?
3. Why do we pop the front when prefix[i] - prefix[front] >= K?
4. Why is the overall time O(n) despite the inner while loops?