862. Shortest Subarray with Sum at Least K
Problem Description
You are given an integer array nums
and an integer k
. Your task is to find the length of the shortest contiguous subarray whose sum is at least k
.
A subarray is a contiguous sequence of elements from the array. For example, if nums = [1, 2, 3, 4]
, then [2, 3]
is a subarray, but [1, 3]
is not (since elements are not contiguous).
The subarray must:
- Be non-empty (contain at least one element)
- Have a sum that is greater than or equal to
k
- Be as short as possible among all valid subarrays
If no such subarray exists that meets these conditions, return -1
.
For example:
- If
nums = [2, -1, 2]
andk = 3
, the shortest subarray with sum ≥ 3 is[2, -1, 2]
with length 3 - If
nums = [1, 2]
andk = 4
, no subarray has sum ≥ 4, so return-1
The challenge involves efficiently finding the minimum length among all possible contiguous subarrays that satisfy the sum requirement.
Intuition
The key insight is to use prefix sums to convert the problem from "finding a subarray with sum ≥ k" to "finding two indices where their prefix sum difference is ≥ k".
If we compute the prefix sum array s
where s[i]
represents the sum of elements from index 0 to i-1, then the sum of any subarray from index i
to j
equals s[j+1] - s[i]
. We need s[j+1] - s[i] >= k
.
The challenge is that we need the shortest such subarray. A brute force approach checking all pairs would take O(n²) time. We need something more efficient.
The crucial observation is that we can use a monotonic deque to maintain potentially useful starting points. As we iterate through the prefix sum array:
-
Why remove elements from the front? If we're at position
i
with prefix sums[i]
, and there's an earlier positionj
wheres[i] - s[j] >= k
, then we found a valid subarray. We can removej
from consideration because any future position would create a longer subarray withj
as the starting point. -
Why maintain monotonicity? If we have two positions
j1 < j2
withs[j1] >= s[j2]
, thenj1
is never useful. Why? Because for any future positioni
, ifs[i] - s[j1] >= k
, thens[i] - s[j2] >= k
as well (sinces[j2] <= s[j1]
), and the subarray starting atj2
would be shorter.
This leads to maintaining a deque of indices with strictly increasing prefix sum values. The deque helps us quickly find the best starting point for each ending position while keeping the time complexity at O(n).
Learn more about Queue, Binary Search, Prefix Sum, Sliding Window, Monotonic Queue and Heap (Priority Queue) patterns.
Solution Approach
The implementation uses a monotonic deque with prefix sums to efficiently find the shortest subarray:
Step 1: Build Prefix Sum Array
s = list(accumulate(nums, initial=0))
This creates a prefix sum array where s[i]
represents the sum of elements from index 0 to i-1. The initial=0
parameter ensures s[0] = 0
, making it easier to handle subarrays starting from index 0.
Step 2: Initialize Data Structures
q = deque() ans = inf
q
: A deque that stores indices of the prefix sum arrayans
: Tracks the minimum length found so far, initialized to infinity
Step 3: Process Each Prefix Sum
For each position i
with prefix sum value v
:
while q and v - s[q[0]] >= k:
ans = min(ans, i - q.popleft())
This checks if the current position forms a valid subarray with the leftmost index in the deque. If v - s[q[0]] >= k
, we found a valid subarray of length i - q[0]
. We remove q[0]
because any future position would create a longer subarray with this starting point.
while q and s[q[-1]] >= v: q.pop()
This maintains the monotonic property of the deque. If the last element in the deque has a prefix sum greater than or equal to the current value v
, we remove it. This is because the current position i
would always be a better starting point (shorter subarray with smaller or equal prefix sum).
q.append(i)
Add the current index to the deque as a potential starting point for future positions.
Step 4: Return Result
return -1 if ans == inf else ans
If ans
remains infinity, no valid subarray was found, so return -1. Otherwise, return the minimum length found.
The algorithm processes each element once, and each index enters and leaves the deque at most once, resulting in O(n) time complexity with O(n) space complexity.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through the algorithm with nums = [2, -1, 2, 1]
and k = 3
.
Step 1: Build prefix sum array
s = [0, 2, 1, 3, 4]
- This represents cumulative sums: s[0]=0, s[1]=0+2=2, s[2]=2+(-1)=1, s[3]=1+2=3, s[4]=3+1=4
Step 2: Process each prefix sum
Iteration i=0, v=0:
- Deque
q = []
- No elements to check for valid subarrays
- No elements to maintain monotonicity
- Add index 0:
q = [0]
ans = inf
Iteration i=1, v=2:
- Check front:
2 - 0 = 2 < 3
, no valid subarray - Check monotonicity:
s[0] = 0 < 2
, keep it - Add index 1:
q = [0, 1]
ans = inf
Iteration i=2, v=1:
- Check front:
1 - 0 = 1 < 3
, no valid subarray - Check monotonicity:
s[1] = 2 >= 1
, remove index 1 - Now
q = [0]
- Add index 2:
q = [0, 2]
ans = inf
Iteration i=3, v=3:
- Check front:
3 - 0 = 3 >= 3
, found valid subarray!- Length =
3 - 0 = 3
- Update
ans = 3
- Remove index 0:
q = [2]
- Length =
- Check front again:
3 - 1 = 2 < 3
, no more valid subarrays - Check monotonicity:
s[2] = 1 < 3
, keep it - Add index 3:
q = [2, 3]
Iteration i=4, v=4:
- Check front:
4 - 1 = 3 >= 3
, found valid subarray!- Length =
4 - 2 = 2
- Update
ans = min(3, 2) = 2
- Remove index 2:
q = [3]
- Length =
- Check front again:
4 - 3 = 1 < 3
, no more valid subarrays - Check monotonicity:
s[3] = 3 < 4
, keep it - Add index 4:
q = [3, 4]
Result: The shortest subarray with sum ≥ 3 has length 2 (the subarray [2, 1]
from indices 2 to 3).
The deque maintains indices with increasing prefix sums, allowing us to efficiently find the shortest valid subarray by checking from the front (oldest, smallest prefix sum) and maintaining monotonicity from the back.
Solution Implementation
1from collections import deque
2from itertools import accumulate
3from typing import List
4
5class Solution:
6 def shortestSubarray(self, nums: List[int], k: int) -> int:
7 # Create prefix sum array with initial value 0
8 # prefix_sums[i] represents sum of nums[0:i]
9 prefix_sums = list(accumulate(nums, initial=0))
10
11 # Deque to maintain indices in increasing order of prefix sum values
12 # This helps us find the shortest valid subarray
13 indices_deque = deque()
14
15 # Initialize minimum length to infinity
16 min_length = float('inf')
17
18 # Iterate through each prefix sum value
19 for current_index, current_sum in enumerate(prefix_sums):
20 # Check if we can form a valid subarray ending at current_index
21 # Remove indices from front while the subarray sum >= k
22 while indices_deque and current_sum - prefix_sums[indices_deque[0]] >= k:
23 # Update minimum length if current subarray is shorter
24 min_length = min(min_length, current_index - indices_deque.popleft())
25
26 # Maintain monotonic increasing property of deque
27 # Remove indices from back if their prefix sum >= current sum
28 # (They won't give us shorter subarrays in the future)
29 while indices_deque and prefix_sums[indices_deque[-1]] >= current_sum:
30 indices_deque.pop()
31
32 # Add current index to the deque
33 indices_deque.append(current_index)
34
35 # Return -1 if no valid subarray found, otherwise return minimum length
36 return -1 if min_length == float('inf') else min_length
37
1class Solution {
2 public int shortestSubarray(int[] nums, int k) {
3 int n = nums.length;
4
5 // Build prefix sum array where prefixSum[i] represents sum from nums[0] to nums[i-1]
6 long[] prefixSum = new long[n + 1];
7 for (int i = 0; i < n; i++) {
8 prefixSum[i + 1] = prefixSum[i] + nums[i];
9 }
10
11 // Deque to maintain indices in increasing order of their prefix sums
12 Deque<Integer> deque = new ArrayDeque<>();
13 int minLength = n + 1; // Initialize with impossible length
14
15 for (int i = 0; i <= n; i++) {
16 // Check if current position forms a valid subarray with any previous position
17 // Remove indices from front while the subarray sum >= k
18 while (!deque.isEmpty() && prefixSum[i] - prefixSum[deque.peekFirst()] >= k) {
19 minLength = Math.min(minLength, i - deque.pollFirst());
20 }
21
22 // Maintain monotonic increasing property of prefix sums in deque
23 // Remove indices from back if their prefix sum is greater than or equal to current
24 while (!deque.isEmpty() && prefixSum[deque.peekLast()] >= prefixSum[i]) {
25 deque.pollLast();
26 }
27
28 // Add current index to deque
29 deque.offerLast(i);
30 }
31
32 // Return -1 if no valid subarray found, otherwise return minimum length
33 return minLength > n ? -1 : minLength;
34 }
35}
36
1class Solution {
2public:
3 int shortestSubarray(vector<int>& nums, int k) {
4 int n = nums.size();
5
6 // Build prefix sum array where prefixSum[i] = sum of nums[0...i-1]
7 vector<long> prefixSum(n + 1);
8 for (int i = 0; i < n; ++i) {
9 prefixSum[i + 1] = prefixSum[i] + nums[i];
10 }
11
12 // Monotonic deque to store indices of prefix sums
13 // Maintains increasing order of prefix sum values
14 deque<int> dq;
15 int minLength = n + 1;
16
17 for (int i = 0; i <= n; ++i) {
18 // Check if current prefix sum minus any previous prefix sum >= k
19 // If yes, update minimum length and remove used indices from front
20 while (!dq.empty() && prefixSum[i] - prefixSum[dq.front()] >= k) {
21 minLength = min(minLength, i - dq.front());
22 dq.pop_front();
23 }
24
25 // Maintain monotonic increasing property of deque
26 // Remove indices from back if their prefix sum >= current prefix sum
27 // This ensures we keep smallest prefix sums for optimal subarray length
28 while (!dq.empty() && prefixSum[dq.back()] >= prefixSum[i]) {
29 dq.pop_back();
30 }
31
32 // Add current index to deque
33 dq.push_back(i);
34 }
35
36 // Return -1 if no valid subarray found, otherwise return minimum length
37 return minLength > n ? -1 : minLength;
38 }
39};
40
1/**
2 * Finds the length of the shortest subarray with sum at least k
3 * Uses a monotonic deque approach with prefix sums
4 *
5 * @param nums - The input array of integers
6 * @param k - The target sum threshold
7 * @returns The length of shortest subarray with sum >= k, or -1 if none exists
8 */
9function shortestSubarray(nums: number[], k: number): number {
10 const arrayLength: number = nums.length;
11 const INFINITY: number = Number.POSITIVE_INFINITY;
12
13 // Build prefix sum array where prefixSum[i] = sum of nums[0...i-1]
14 const prefixSum: number[] = Array(arrayLength + 1).fill(0);
15 for (let i = 0; i < arrayLength; i++) {
16 prefixSum[i + 1] = prefixSum[i] + nums[i];
17 }
18
19 // Monotonic deque to store indices of prefix sums
20 // Maintains increasing order of prefix sum values
21 const deque: number[] = [];
22 let minLength: number = INFINITY;
23
24 // Process each prefix sum
25 for (let currentIndex = 0; currentIndex <= arrayLength; currentIndex++) {
26 // Check if we can form a valid subarray ending at currentIndex
27 // Remove indices from front while the subarray sum is at least k
28 while (deque.length > 0 &&
29 prefixSum[currentIndex] - prefixSum[deque[0]] >= k) {
30 const startIndex: number = deque.shift()!;
31 minLength = Math.min(minLength, currentIndex - startIndex);
32 }
33
34 // Maintain monotonic property of the deque
35 // Remove indices from back with prefix sum >= current prefix sum
36 // These indices won't give us shorter subarrays in the future
37 while (deque.length > 0 &&
38 prefixSum[currentIndex] <= prefixSum[deque[deque.length - 1]]) {
39 deque.pop();
40 }
41
42 // Add current index to the deque
43 deque.push(currentIndex);
44 }
45
46 // Return result: -1 if no valid subarray found, otherwise the minimum length
47 return minLength === INFINITY ? -1 : minLength;
48}
49
Time and Space Complexity
Time Complexity: O(n)
where n
is the length of the input array nums
.
- Computing the prefix sum array using
accumulate
takesO(n)
time. - The main loop iterates through each element of the prefix sum array once, which is
O(n+1) = O(n)
. - Within the loop:
- Each element is added to the deque exactly once via
q.append(i)
. - Each element can be removed from the front of the deque at most once via
q.popleft()
. - Each element can be removed from the back of the deque at most once via
q.pop()
.
- Each element is added to the deque exactly once via
- Since each element enters and leaves the deque at most once, the total operations across all iterations is
O(n)
. - Therefore, the overall time complexity is
O(n) + O(n) = O(n)
.
Space Complexity: O(n)
where n
is the length of the input array nums
.
- The prefix sum array
s
storesn+1
elements, requiringO(n)
space. - The deque
q
can store at mostn+1
indices in the worst case, requiringO(n)
space. - Other variables (
ans
,i
,v
) use constant spaceO(1)
. - Therefore, the overall space complexity is
O(n) + O(n) + O(1) = O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Attempting Sliding Window with Negative Numbers
The most common pitfall is trying to use a standard two-pointer sliding window approach when the array contains negative numbers. This approach works perfectly for arrays with only non-negative numbers but fails with negative values.
Why it fails: With negative numbers, the subarray sum doesn't monotonically increase as we expand the window. Adding more elements might actually decrease the sum, breaking the fundamental assumption of the sliding window technique.
Example of incorrect approach:
# INCORRECT - This won't work with negative numbers!
def shortestSubarray_wrong(nums, k):
left = 0
current_sum = 0
min_length = float('inf')
for right in range(len(nums)):
current_sum += nums[right]
while current_sum >= k:
min_length = min(min_length, right - left + 1)
current_sum -= nums[left]
left += 1
return -1 if min_length == float('inf') else min_length
Test case where it fails:
nums = [2, -1, 2] k = 3 # The correct answer is 3 (entire array) # But sliding window might miss this because after removing the first 2, # we get sum = 1, which is less than k
2. Not Maintaining Monotonic Property Correctly
Another pitfall is forgetting to maintain the monotonic increasing property of the deque. Without this step, the algorithm becomes inefficient and might not find the optimal solution.
Why we need it: If we have two indices i < j where prefix_sum[i] >= prefix_sum[j], then j is always a better starting point for any future position because:
- It comes later (creating shorter subarrays)
- It has a smaller or equal prefix sum (making it easier to reach the target k)
3. Incorrect Prefix Sum Initialization
Using accumulate(nums)
without initial=0
creates a prefix sum array where the first element is nums[0] instead of 0. This makes it harder to handle subarrays starting from index 0.
Incorrect:
prefix_sums = list(accumulate(nums)) # First element is nums[0]
Correct:
prefix_sums = list(accumulate(nums, initial=0)) # First element is 0
4. Not Handling Edge Cases
Forgetting to check if the deque is empty before accessing elements can cause index errors:
# Always check if deque is not empty before accessing while indices_deque and current_sum - prefix_sums[indices_deque[0]] >= k: # Process...
Solution: The provided implementation correctly addresses all these pitfalls by:
- Using a monotonic deque approach that handles negative numbers
- Maintaining the deque's monotonic property by removing larger prefix sums from the back
- Using
initial=0
in accumulate for proper prefix sum initialization - Always checking if the deque is non-empty before accessing elements
What's the output of running the following function using the following tree as input?
1def serialize(root):
2 res = []
3 def dfs(root):
4 if not root:
5 res.append('x')
6 return
7 res.append(root.val)
8 dfs(root.left)
9 dfs(root.right)
10 dfs(root)
11 return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4 StringJoiner res = new StringJoiner(" ");
5 serializeDFS(root, res);
6 return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10 if (root == null) {
11 result.add("x");
12 return;
13 }
14 result.add(Integer.toString(root.val));
15 serializeDFS(root.left, result);
16 serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2 let res = [];
3 serialize_dfs(root, res);
4 return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8 if (!root) {
9 res.push("x");
10 return;
11 }
12 res.push(root.val);
13 serialize_dfs(root.left, res);
14 serialize_dfs(root.right, res);
15}
16
Recommended Readings
Queue Intro Think of the last time you stood in line to buy movie tickets The first person in line gets their ticket first and any newcomers join the end of the line This system where the first person to arrive is the first to be served is a queue in real
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
Want a Structured Path to Master System Design Too? Don’t Miss This!