DSA sheet · Arrays · Sliding Window pattern
Minimum Size Subarray Sum
The teacher calls this the start of the sliding window section after finishing two pointers. It's a medium problem, and it's the classic "shortest valid window" question. She first writes a brute force (two loops with an early break), explains why the answer must start at "infinity" and why we must return 0 when nothing works, then picks sliding window from her four array patterns. Her picture for sliding window is a leech: it stretches its front forward, then pulls its back in.
Why it matters: "shortest subarray that reaches a target" is the mirror image of "longest subarray that stays within a limit". The only real difference is where you record the answer, and this problem teaches that.
Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the logic from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember
- Part 0 · What you must know before starting (sliding window from scratch)
- Part A · Brute force: every start, stop at the first valid end
- Part B · Optimal: expand until valid, shrink while still valid
- Part C · Revision page
Part 0 · Before starting
What is a subarray?
A subarray is a piece of the array whose elements sit next to each other (contiguous). Choose a start index and an end index and take everything in between, with no skipping. (In strings this is called a substring.) Its length is end − start + 1.
The brute force over all windows, and why it repeats work
Two loops can check every subarray: one fixes the start, the other walks the end forward. There are n·(n+1)/2 subarrays, so this is O(n²). The waste: after checking [2, 3, 1, 2], the next start begins again with [3], re-adding 3, 1, 2 that we had already added. Sliding window keeps the running sum and only fixes the edges.
The window [left..right]: expand and shrink
A window is the current subarray, marked by two pointers: left (first index) and right (last index). We keep its sum in a variable so we never re-add it.
- Expand: move
rightforward,sum += nums[right]. - Shrink:
sum -= nums[left], then moveleftforward.
The teacher's picture: a leech walking. It stretches its front end forward (that's right, the expansion), then pulls its back end up (that's left, the shrinking), and repeats. Both ends only ever move forward. When to expand and when to shrink depends on the question.
Fixed-size vs variable-size windows
| Fixed-size window | Variable-size window | |
|---|---|---|
| question looks like | "every subarray of size k…" | "longest / shortest / how many subarrays such that…" |
| how it moves | add the new right element; once the size passes k, remove the element k steps back | grow with right; shrink with left depending on the rule |
| this problem? | no, we're looking for the size | yes |
Longest vs shortest vs count
| kind | shrink when… | record the answer… |
|---|---|---|
| longest valid | the window becomes invalid | after shrinking: best = max(best, right − left + 1) |
| shortest valid (this problem) | the window is valid (try to make it shorter) | inside the shrinking loop, before each shrink: best = min(best, right − left + 1) |
| count of valid | the window becomes invalid | after shrinking: count += right − left + 1 (every valid subarray ending at right) |
Another trick you'll meet later: "exactly K" = atMost(K) − atMost(K − 1). Not needed here.
Why sliding window needs positive numbers
Shrinking only makes sense if the rule is monotonic: adding an element must always push the sum up, and removing one must always push it down. With all numbers positive (here every nums[i] ≥ 1), that's guaranteed. If negatives were allowed, adding could lower the sum and removing could raise it, so the window could stop too early and miss answers. (That's why "Subarray Sum Equals K", which allows negatives, uses prefix sums instead.)
Other tools in this pattern (not needed here)
- Frequency map (
dict/collections.Counter): value → how many times it's in the window, for rules about distinct values. - Monotonic deque: keeps indexes in decreasing order of value, used only for Sliding Window Maximum.
The teacher's 4 array patterns
| pattern | when it fits |
|---|---|
| Two pointers | only two particular points matter, and we compute something from just those two |
| Sliding window | two points and everything in between matter |
| Prefix sum | many queries like "sum from index a to index b?" |
| Kadane's | maximum sum when numbers can be negative |
Part A · Brute force: every start, stop at the first valid end
LeetCode 209
1The question in simple words
You get an array of positive integers nums and a positive integer target. Among all subarrays whose sum is greater than or equal to target, return the smallest length. If no subarray reaches target, return 0.
Many subarrays reach 7 ([2,3,1,2] = 8, [3,1,2,4] = 10, [1,2,4] = 7, …). We don't want all of them, only the length of the shortest one.
2What the constraints tell us
- 1 ≤ target ≤ 10⁹. An int holds up to about 2.1·10⁹ (INT_MAX), so it fits. (Python ints never overflow anyway.)
- 1 ≤ n ≤ 10⁵. O(n) is 10⁵ steps. O(n²) is 10¹⁰, far past the ~10⁸ limit, so an O(n²) solution will not pass. We must optimise, but she shows the brute force first.
- 1 ≤ nums[i] ≤ 10⁴. All numbers are positive (they start at 1). No negatives → sums only grow as a subarray gets longer. This is what allows both the early
breakand the sliding window. (She reads the limit as 10⁵ once and 10⁴ later; LeetCode says 10⁴. The largest possible sum, 10⁵ × 10⁴ = 10⁹, is still safe.)
3Intuition: try every start, extend until it's enough
Fix a starting index i. Move a second pointer j from i forward, adding each number to a running sum. As soon as the sum reaches target, we have the shortest valid subarray that starts at i. Record its length and move on to the next start.
4Building the logic from the example
Start at index 0 (value 2), target 7
| j | subarray | sum | ≥ 7? |
|---|---|---|---|
| 0 | [2] | 2 | no |
| 1 | [2,3] | 5 | no |
| 2 | [2,3,1] | 6 | no |
| 3 | [2,3,1,2] | 8 | yes → length 3 − 0 + 1 = 4 |
Why break as soon as it's valid
[2,3,1,2,4] also has sum ≥ 7, and so does [2,3,1,2,4,3]. They're valid, but they're longer, and we want the minimum length. Going further from the same start can only give longer subarrays, so the extra work is useless → break.
→ It's safe even without that. From a fixed start, the first
j that works gives the shortest length for that start. Anything later is longer. (Positivity is what we need for the sliding window later.)The length: j − i + 1
Think of i as the left pointer and j as the right pointer. The number of elements from i to j is j − i + 1. Here 3 − 0 + 1 = 4.
Keeping the minimum, and why it starts at "infinity"
We keep an answer variable and update it with ans = min(ans, length). What should ans start as?
- If it started at 0, then min(0, 4) = 0, min(0, 3) = 0… it would stay 0 forever. Wrong.
- So it must start at a huge value that any real length beats. In Java that's
Integer.MAX_VALUE. In Python we usefloat('inf').
The other starts
- Start 1 (3): 3, 4, 6, 10 → valid at length 4. min(4, 4) = 4, no change. Break.
- Start 2 (1): 1, 3, 7 → 7 ≥ 7 (equal counts!) → length 3. ans = 3. Break.
- Start 3 (2): 2, 6, 9 → length 3. Still 3. Break.
- Start 4 (4): 4, 7 → length 2. ans = 2. Break.
- Start 5 (3): 3, end of array. Never valid.
The case she almost forgot: no valid subarray → 0
Halfway through, the teacher realises something is missing. Suppose the whole array adds up to only 15 (2+3+1+2+4+3) but target is 20. No subarray can reach it, so ans is never updated and is still infinity. We can't return infinity. The question says to return 0 in that case.
return 0 if ans == float('inf') else ans — "still infinity" means "never found a valid subarray".5Approach steps
ans = infinity.- For each start
i:total = 0(fresh sum for this start). - For
jfromito n − 1: addnums[j]. - If
total ≥ target:ans = min(ans, j − i + 1)andbreak. - After both loops: return 0 if ans is still infinity, else ans.
6Code (Python)
class Solution:
def minSubArrayLen(self, target, nums):
n = len(nums)
ans = float('inf') # "nothing found yet"
for i in range(n): # start of the subarray
total = 0 # fresh sum for this start
for j in range(i, n): # end of the subarray
total += nums[j]
if total >= target: # first valid end for this start
ans = min(ans, j - i + 1)
break # longer ones can't be better
return 0 if ans == float('inf') else ans7Code line by line
| line | what it means |
|---|---|
| ans = float('inf') | Start bigger than any possible length, so the first real length replaces it. Starting at 0 would break min. |
| for i in range(n): | Every index gets a turn as the start. |
| total = 0 | Reset the sum for the new start. It must be inside the outer loop so the old start's sum doesn't leak in. |
| for j in range(i, n): total += nums[j] | Extend the subarray to the right by one element. Now total = sum(nums[i..j]). |
| if total >= target: | "Greater than or equal". A sum exactly equal to target counts. |
| ans = min(ans, j - i + 1) | Length of this subarray, keep it if it's the shortest so far. |
| break | From this start, any further end only makes it longer. |
| return 0 if ans == float('inf') else ans | No valid subarray anywhere → 0. Otherwise the shortest length. |
8Dry run (hand table)
nums = [2, 3, 1, 2, 4, 3], target = 7.
| start i | running sums as j moves | first valid end | length | ans after |
|---|---|---|---|---|
| 0 | 2, 5, 6, 8 ✓ | j = 3 | 4 | 4 |
| 1 | 3, 4, 6, 10 ✓ | j = 4 | 4 | 4 |
| 2 | 1, 3, 7 ✓ | j = 4 | 3 | 3 |
| 3 | 2, 6, 9 ✓ | j = 5 | 3 | 3 |
| 4 | 4, 7 ✓ | j = 5 | 2 | 2 |
| 5 | 3 (array ends) | none | – | 2 |
Final answer: 2 ✓ ([4, 3]).
9Complexity & remember
- Time O(n²):
ivisits each index once, and for eachi,jcan run nearly to the end (n, then n − 1, then n − 2 …). With n = 10⁵ that's about 10¹⁰ in the worst case → TLE. The teacher says submitting it would time out because it's far too slow. - Space O(1).
j − i + 1 and break. Start ans at infinity, return 0 if it never changed.Part B · Optimal: expand until valid, shrink while still valid
LeetCode 209
1The question again, with the new goal
Same question. The goal now is O(n): one pass where left never goes back to restart.
2Choosing the pattern
- Two pointers ✗: it works with two particular points and only those two. Here the sum needs every number between the two points.
- Sliding window ✓: two points and everything in between. The subarray sum from i to j needs every number between them.
- Prefix sum ✗: it shines when we're asked many range-sum queries ("sum from this index to that index?"). We aren't, so it isn't needed.
- Kadane's ✗: for maximum sums with negative numbers. The constraints say numbers start at 1, so there are no negatives.
3Intuition: the leech
left and right start at 0, and the window sum starts at 0.
- Expand (stretch the front): keep moving
rightand adding, as long as the sum is still below target. A short window that isn't enough yet is no use. - The moment the sum is ≥ target, we have a valid window. Record its length. But maybe it's longer than it needs to be.
- Shrink (pull the back in): moving
rightfurther would only make it longer, so to get shorter we must moveleft. Remove from the left and record again, as long as it stays valid. - Once it drops below target, go back to expanding.
4Building the logic from examples
First, the expansion on [2, 3, 1, 2, 4, 3], target 7
right = 0: sum 2, not enough. right = 1: sum 5. right = 2: sum 6. Still below 7, so nothing to record and no reason to shrink. Just keep moving right (the for loop does this for us).
right = 3: sum 8 ≥ 7. Valid! Length right − left + 1 = 3 − 0 + 1 = 4. We don't know yet if it's the minimum, but it's a valid length, so ans = min(ans, 4) = 4.
Then the shrink: subtract the left value, move left
To make the window shorter we take out the left element: total -= nums[left], then left += 1. Removing 2: sum 8 → 6. Now 6 < 7, the window [3, 1, 2] is no longer valid, so we stop shrinking and go back to expanding.
Why while, not if: her [1, 1, 1, 5] example
The teacher asks: should the shrink happen once (if) or repeatedly (while)? Take nums = [1, 1, 1, 5], target = 6.
With an if, we'd shrink once and report 3. The correct answer is 2. So we keep shrinking until the condition becomes false. The last length recorded before it broke is the shortest valid window for this right end.
Where exactly to record the answer
Inside the while, we record the length before removing the left element. At that moment the window is guaranteed valid (that's what the while condition just checked). After the loop ends, the window is invalid, so recording there would be wrong. This is the opposite of "longest window" problems, where you record after shrinking.
> or >=?→
>=. The question says "greater than or equal to", and her own examples rely on it ([1, 2, 4] with sum exactly 7, and [1, 5] with sum exactly 6, are valid). With > you'd miss those.left moves past an index, could the best answer still start at that index (with a later right)?→ No. We only move left past index l when the window [l..right] was valid, and we recorded its length then. Any window [l..later right] is longer than that, so it can never be shorter than what we already recorded. That's why left never needs to go back, and the brute force's restarts were wasted work.
left run past right?→ No. If the window became empty, its sum would be 0, and 0 ≥ target is impossible because target ≥ 1. So the while loop always stops while at least one element is still inside.
Continuing her dry run
- right = 4 (value 4): sum 6 + 4 = 10 ≥ 7. Window [3, 1, 2, 4], length 4, not better than 4. Shrink: remove 3 → sum 7, still valid → window [1, 2, 4], length 3 → ans 3. Remove 1 → sum 6, invalid → stop.
- right = 5 (value 3): sum 9 ≥ 7. Window [2, 4, 3], length 3, no change. Shrink: remove 2 → sum 7, valid → window [4, 3], length 2 → ans 2. Remove 4 → sum 3, invalid → stop.
- right reaches the end. Answer 2 ✓.
5Approach steps
left = 0,total = 0,ans = infinity.- For each
right:total += nums[right](expand). - While
total ≥ target: recordans = min(ans, right − left + 1), thentotal -= nums[left]andleft += 1(shrink). - After the loop: return 0 if ans is still infinity, else ans.
6Code (Python)
class Solution:
def minSubArrayLen(self, target, nums):
total = 0 # sum of the current window
left = 0
min_len = float('inf') # "nothing found yet"
for right in range(len(nums)):
total += nums[right] # expand: stretch the front
while total >= target: # valid: try to make it shorter
min_len = min(min_len, right - left + 1) # record first
total -= nums[left] # then pull the back in
left += 1
return 0 if min_len == float('inf') else min_len7Code line by line
| line | what it means |
|---|---|
| total = 0 left = 0 min_len = float('inf') | Empty window starting at index 0. min_len starts at infinity for the same reason as in Part A. |
| for right in range(len(nums)): | The front of the leech: visits every index once. Moving to the next index is the "expand" step. |
| total += nums[right] | Add the new element. total = sum(nums[left..right]). |
| while total >= target: | The window is valid. Keep trying to shorten it, as many times as it stays valid. |
| min_len = min(min_len, right - left + 1) | Record this valid window's length before shrinking (after shrinking it may be invalid). |
| total -= nums[left] left += 1 | Remove the leftmost element from the sum, then drop it from the window. |
| return 0 if min_len == float('inf') else min_len | The teacher's ternary: if nothing valid was found, 0, otherwise the minimum length. |
8Dry run
nums = [2, 3, 1, 2, 4, 3], target = 7. In the shrink column, each line is one pass of the while loop.
| step | right (value added) | window [left..right] | sum after adding | shrink? (what leaves, why) | window after shrinking | min_len so far |
|---|---|---|---|---|---|---|
| 1 | 0 (2) | [2] (0..0) | 2 | no, 2 < 7 | [2] (0..0) | ∞ |
| 2 | 1 (3) | [2,3] (0..1) | 5 | no, 5 < 7 | [2,3] (0..1) | ∞ |
| 3 | 2 (1) | [2,3,1] (0..2) | 6 | no, 6 < 7 | [2,3,1] (0..2) | ∞ |
| 4 | 3 (2) | [2,3,1,2] (0..3) | 8 | 8 ≥ 7: record 4, 2 leaves → 6 < 7 stop | [3,1,2] (1..3) | 4 |
| 5 | 4 (4) | [3,1,2,4] (1..4) | 10 | 10 ≥ 7: record 4, 3 leaves → 7 7 ≥ 7: record 3, 1 leaves → 6 < 7 stop | [2,4] (3..4) | 3 |
| 6 | 5 (3) | [2,4,3] (3..5) | 9 | 9 ≥ 7: record 3, 2 leaves → 7 7 ≥ 7: record 2, 4 leaves → 3 < 7 stop | [3] (5..5) | 2 |
The array at the key moments (yellow = window, grey = left behind):
Final answer: 2 ✓, the same as the brute force.
9Complexity & remember
- Time O(n):
righttouches each index once.leftalso touches each index at most once, because it never goes back and restarts for a new right. Thewhileinside theforlooks nested, but the total moves are n + n = 2n → O(n). - Space O(1): three variables.
She submitted it and it beat about 99.7%, as expected for a linear solution.
total += x → while total ≥ target: record right − left + 1, total -= nums[left], left += 1.Shortest-window problems record inside the while, before shrinking. Return 0 if nothing was recorded.
Part C · Revision page
| Brute force | Sliding window | |
|---|---|---|
| idea | for each start, extend until sum ≥ target, record, break | expand until valid, then shrink while valid, recording each length |
| loops | two nested (j restarts for every i) | one for + a while that never restarts |
| answer starts at | infinity (float('inf')), and becomes 0 at the end if never updated | |
| time / space | O(n²) / O(1), TLE for n = 10⁵ | O(n) (≈2n moves) / O(1) |
| Longest valid window | Shortest valid window (this one) | |
|---|---|---|
| shrink while… | the window is invalid | the window is valid |
| record… | after the while: max | inside the while, before removing: min |
| answer starts at | 0 | infinity |
2. Expand right until the sum is ≥ target.
3. Then while it's still ≥ target: record the length, remove nums[left], move left.
4. Start the answer at infinity, not 0.
5. If it's still infinity at the end, return 0.
min_len at 0✗ forgetting to return 0 when no window works
✗
if instead of while for shrinking (misses [1, 5] in [1, 1, 1, 5])✗
total > target instead of >=✗ recording the length after the while loop (the window is invalid there)
✗ using this on arrays with negative numbers
s = Solution() print(s.minSubArrayLen(7, [2, 3, 1, 2, 4, 3])) # 2 print(s.minSubArrayLen(6, [1, 1, 1, 5])) # 2 (needs while, not if) print(s.minSubArrayLen(4, [1, 4, 4])) # 1 print(s.minSubArrayLen(11, [1, 1, 1, 1, 1, 1])) # 0 (no valid window) print(s.minSubArrayLen(20, [2, 3, 1, 2, 4, 3])) # 0 (total is only 15) print(s.minSubArrayLen(15, [2, 3, 1, 2, 4, 3])) # 6 (the whole array)
Based on this video: Minimum Size Subarray Sum | Sliding Window