Facebook Pixel

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] and k = 3, the shortest subarray with sum ≥ 3 is [2, -1, 2] with length 3
  • If nums = [1, 2] and k = 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Why remove elements from the front? If we're at position i with prefix sum s[i], and there's an earlier position j where s[i] - s[j] >= k, then we found a valid subarray. We can remove j from consideration because any future position would create a longer subarray with j as the starting point.

  2. Why maintain monotonicity? If we have two positions j1 < j2 with s[j1] >= s[j2], then j1 is never useful. Why? Because for any future position i, if s[i] - s[j1] >= k, then s[i] - s[j2] >= k as well (since s[j2] <= s[j1]), and the subarray starting at j2 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 array
  • ans: 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 Evaluator

Example 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]
  • 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]
  • 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 takes O(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().
  • 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 stores n+1 elements, requiring O(n) space.
  • The deque q can store at most n+1 indices in the worst case, requiring O(n) space.
  • Other variables (ans, i, v) use constant space O(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:

  1. Using a monotonic deque approach that handles negative numbers
  2. Maintaining the deque's monotonic property by removing larger prefix sums from the back
  3. Using initial=0 in accumulate for proper prefix sum initialization
  4. Always checking if the deque is non-empty before accessing elements
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More