241 lines
10 KiB
YAML
241 lines
10 KiB
YAML
title: Minimum Size Subarray Sum
|
|
slug: minimum-size-subarray-sum
|
|
difficulty: medium
|
|
leetcode_id: 209
|
|
leetcode_url: https://leetcode.com/problems/minimum-size-subarray-sum/
|
|
categories:
|
|
- arrays
|
|
- binary-search
|
|
patterns:
|
|
- slug: sliding-window
|
|
is_optimal: false
|
|
- slug: binary-search
|
|
is_optimal: true
|
|
- slug: prefix-sum
|
|
is_optimal: false
|
|
|
|
function_signature: "def min_subarray_len(target: int, nums: list[int]) -> int:"
|
|
|
|
test_cases:
|
|
visible:
|
|
- input: { target: 7, nums: [2, 3, 1, 2, 4, 3] }
|
|
expected: 2
|
|
- input: { target: 4, nums: [1, 4, 4] }
|
|
expected: 1
|
|
- input: { target: 11, nums: [1, 1, 1, 1, 1, 1, 1, 1] }
|
|
expected: 0
|
|
hidden:
|
|
- input: { target: 5, nums: [5] }
|
|
expected: 1
|
|
- input: { target: 6, nums: [5] }
|
|
expected: 0
|
|
- input: { target: 15, nums: [1, 2, 3, 4, 5] }
|
|
expected: 5
|
|
- input: { target: 100, nums: [1, 1, 1, 1, 1, 1, 1] }
|
|
expected: 0
|
|
- input: { target: 3, nums: [1, 1, 1, 1, 1] }
|
|
expected: 3
|
|
|
|
description: |
|
|
Given an array of positive integers `nums` and a positive integer `target`, return *the **minimal length** of a subarray whose sum is greater than or equal to* `target`. If there is no such subarray, return `0` instead.
|
|
|
|
A **subarray** is a contiguous non-empty sequence of elements within an array.
|
|
|
|
constraints: |
|
|
- `1 <= target <= 10^9`
|
|
- `1 <= nums.length <= 10^5`
|
|
- `1 <= nums[i] <= 10^4`
|
|
|
|
examples:
|
|
- input: "target = 7, nums = [2,3,1,2,4,3]"
|
|
output: "2"
|
|
explanation: "The subarray [4,3] has the minimal length under the problem constraint."
|
|
- input: "target = 4, nums = [1,4,4]"
|
|
output: "1"
|
|
explanation: "The subarray [4] satisfies the condition with length 1."
|
|
- input: "target = 11, nums = [1,1,1,1,1,1,1,1]"
|
|
output: "0"
|
|
explanation: "No subarray sums to 11 or greater (total sum is only 8)."
|
|
|
|
explanation:
|
|
intuition: |
|
|
Imagine you have a **stretchy window** that you slide across the array. The window has two ends: a left boundary and a right boundary. Your goal is to find the smallest window whose elements sum to at least `target`.
|
|
|
|
Here's the key insight: since all numbers are **positive**, adding more elements to the window can only **increase** the sum, and removing elements can only **decrease** it. This monotonic property is what makes the sliding window technique work.
|
|
|
|
Think of it like this: you start by expanding the right end of the window, adding elements until the sum reaches the target. Once it does, you try to **shrink** the window from the left to see if you can achieve the same sum with fewer elements. You keep track of the smallest valid window you find.
|
|
|
|
This "expand then contract" pattern is the essence of the **sliding window** technique for finding minimum-length subarrays.
|
|
|
|
approach: |
|
|
We solve this using the **Sliding Window** technique:
|
|
|
|
**Step 1: Initialise variables**
|
|
|
|
- `left = 0`: Left boundary of our window
|
|
- `current_sum = 0`: Running sum of elements in the window
|
|
- `min_length = infinity`: Track the smallest valid window (use infinity so any valid window becomes the minimum)
|
|
|
|
|
|
|
|
**Step 2: Expand the window by moving the right pointer**
|
|
|
|
- Iterate `right` from `0` to `n - 1`
|
|
- Add `nums[right]` to `current_sum` — this expands the window
|
|
- After each expansion, check if the sum meets or exceeds `target`
|
|
|
|
|
|
|
|
**Step 3: Contract the window while the sum is valid**
|
|
|
|
- While `current_sum >= target`:
|
|
- Update `min_length` with the current window size: `right - left + 1`
|
|
- Remove `nums[left]` from `current_sum` — this shrinks the window
|
|
- Move `left` forward: `left += 1`
|
|
- This inner loop finds the minimum window ending at the current `right` position
|
|
|
|
|
|
|
|
**Step 4: Return the result**
|
|
|
|
- If `min_length` is still infinity, no valid subarray exists — return `0`
|
|
- Otherwise, return `min_length`
|
|
|
|
|
|
|
|
The sliding window works because all elements are positive: once the sum drops below `target`, we need to expand right again (no point shrinking further).
|
|
|
|
common_pitfalls:
|
|
- title: Using a Brute Force Nested Loop
|
|
description: |
|
|
A naive approach tries every possible subarray:
|
|
- Outer loop for start index
|
|
- Inner loop for end index
|
|
- Sum calculation for each subarray
|
|
|
|
This results in **O(n^2)** or **O(n^3)** time complexity. With `nums.length <= 10^5`, this will cause **Time Limit Exceeded (TLE)**.
|
|
|
|
The sliding window achieves O(n) because each element is added and removed at most once.
|
|
wrong_approach: "Nested loops for all subarrays"
|
|
correct_approach: "Sliding window with two pointers"
|
|
|
|
- title: Forgetting Elements Are Positive
|
|
description: |
|
|
The sliding window technique **only works here because all elements are positive**. This guarantees:
|
|
- Expanding the window always increases the sum
|
|
- Shrinking the window always decreases the sum
|
|
|
|
If negative numbers were allowed, you couldn't shrink the window confidently — removing a negative number would increase the sum!
|
|
|
|
For arrays with negatives, you'd need a different approach (like prefix sums with binary search or monotonic deque).
|
|
wrong_approach: "Applying sliding window blindly to any subarray sum problem"
|
|
correct_approach: "Verify elements are positive before using sliding window"
|
|
|
|
- title: Returning min_length Without Checking Validity
|
|
description: |
|
|
If no subarray sums to `target` or more, `min_length` remains infinity. Returning this would be incorrect.
|
|
|
|
Always check: `return 0 if min_length == infinity else min_length`
|
|
|
|
Example: `target = 11, nums = [1,1,1,1,1,1,1,1]` — total sum is 8, so no valid subarray exists.
|
|
wrong_approach: "return min_length"
|
|
correct_approach: "return 0 if min_length == float('inf') else min_length"
|
|
|
|
key_takeaways:
|
|
- "**Sliding Window** is the go-to technique for contiguous subarray problems with monotonic conditions"
|
|
- "**Positive elements enable shrinking**: You can confidently shrink the window because removing elements always decreases the sum"
|
|
- "**Two-pointer pattern**: The left pointer never moves backward — each element is processed at most twice (once added, once removed)"
|
|
- "**O(n) despite nested loops**: The inner while loop is amortized O(1) because `left` moves at most `n` times total"
|
|
|
|
time_complexity: "O(n). Each element is added to the window once (when `right` passes it) and removed at most once (when `left` passes it). The inner while loop executes at most `n` times total across all iterations."
|
|
space_complexity: "O(1). We only use a fixed number of variables (`left`, `current_sum`, `min_length`) regardless of input size."
|
|
|
|
solutions:
|
|
- approach_name: Sliding Window
|
|
is_optimal: true
|
|
code: |
|
|
def min_subarray_len(target: int, nums: list[int]) -> int:
|
|
n = len(nums)
|
|
left = 0
|
|
current_sum = 0
|
|
min_length = float('inf') # Use infinity so any valid window becomes min
|
|
|
|
# Expand window by moving right pointer
|
|
for right in range(n):
|
|
current_sum += nums[right] # Add element to window
|
|
|
|
# Contract window while sum is valid
|
|
while current_sum >= target:
|
|
# Update minimum length
|
|
min_length = min(min_length, right - left + 1)
|
|
# Remove leftmost element and shrink window
|
|
current_sum -= nums[left]
|
|
left += 1
|
|
|
|
# Return 0 if no valid subarray found
|
|
return 0 if min_length == float('inf') else min_length
|
|
explanation: |
|
|
**Time Complexity:** O(n) — Each element is added and removed from the window at most once.
|
|
|
|
**Space Complexity:** O(1) — Only a few variables needed.
|
|
|
|
The sliding window expands by moving `right` and contracts by moving `left`. Because all elements are positive, once the sum drops below `target`, we must expand again. This guarantees optimal efficiency.
|
|
|
|
- approach_name: Binary Search with Prefix Sums
|
|
is_optimal: false
|
|
code: |
|
|
import bisect
|
|
|
|
def min_subarray_len(target: int, nums: list[int]) -> int:
|
|
n = len(nums)
|
|
min_length = float('inf')
|
|
|
|
# Build prefix sum array: prefix[i] = sum of nums[0..i-1]
|
|
prefix = [0] * (n + 1)
|
|
for i in range(n):
|
|
prefix[i + 1] = prefix[i] + nums[i]
|
|
|
|
# For each starting position, binary search for the ending position
|
|
for i in range(n):
|
|
# We need prefix[j] - prefix[i] >= target
|
|
# So prefix[j] >= prefix[i] + target
|
|
needed = prefix[i] + target
|
|
# Find smallest j where prefix[j] >= needed
|
|
j = bisect.bisect_left(prefix, needed)
|
|
|
|
if j <= n: # Valid ending position found
|
|
min_length = min(min_length, j - i)
|
|
|
|
return 0 if min_length == float('inf') else min_length
|
|
explanation: |
|
|
**Time Complexity:** O(n log n) — Building prefix sums is O(n), and we do n binary searches of O(log n) each.
|
|
|
|
**Space Complexity:** O(n) — For the prefix sum array.
|
|
|
|
This approach builds a prefix sum array, then for each starting index, binary searches for the smallest ending index that achieves the target sum. Works because prefix sums are monotonically increasing (all elements positive).
|
|
|
|
- approach_name: Brute Force
|
|
is_optimal: false
|
|
code: |
|
|
def min_subarray_len(target: int, nums: list[int]) -> int:
|
|
n = len(nums)
|
|
min_length = float('inf')
|
|
|
|
# Try every starting position
|
|
for i in range(n):
|
|
current_sum = 0
|
|
# Extend to every ending position
|
|
for j in range(i, n):
|
|
current_sum += nums[j]
|
|
if current_sum >= target:
|
|
min_length = min(min_length, j - i + 1)
|
|
break # Found minimum for this start, move on
|
|
|
|
return 0 if min_length == float('inf') else min_length
|
|
explanation: |
|
|
**Time Complexity:** O(n^2) — Nested loops over all starting and ending positions.
|
|
|
|
**Space Complexity:** O(1) — Only tracking current sum and minimum.
|
|
|
|
This checks every possible subarray starting position, extending until the sum meets the target. The `break` optimises slightly (we stop once we hit target), but worst case is still O(n^2). Too slow for large inputs.
|