Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Two Pointers & Sliding Window

Builds on Longest Window, which you haven’t finished yet.

P1MediumBuilt on Longest Window~4 min speedrun

Variable-Size Sliding Window (Minimum Window)

Built on
Longest WindowShrink the window while it is still valid and record its length each time, instead of shrinking only when it becomes invalid.
The problem

Minimum Size Subarray Sum (LC 209)(opens leetcode.com)

nums is an array of positive integers, and target is a positive integer. Among all contiguous subarrays whose sum reaches at least target, find the shortest one and return its length — or 0 if no subarray sums that high.

Example 1
Input:target = 7, nums = [2,3,1,2,4,3]
Output:2
The subarray [4,3] sums to 7, meeting the target with the fewest elements possible.

Shortest Window

left -> right -> (Expand to Validate, Shrink to Minimize)
Spot it when: You want the shortest contiguous subarray or substring that meets a condition, where adding elements only makes the condition easier to meet.
Shortest Window
Python
1from typing import Callable, List, Tuple
2 
3 
4def shortest_window(n: int, add: Callable[[int], None], remove: Callable[[int], None], is_valid: Callable[[], bool]) -> Tuple[float, int]:
5 """Shortest Window: the shortest valid window [left, right] of indices 0..n-1, as (length, left).
6 Returns (0, 0) when the empty window is already valid, and (float('inf'), -1) when no window is valid.
7 Precondition: every longer window that holds a valid window is valid too.
8 add(i) and remove(i) update the window's state for index i.
9 Smallest Range Covering Elements from K Lists does not fit, because it measures a range of values, not a length.
10 T = the cost of one add, remove or is_valid call.
11 Time: O(N * T) | Space: O(1) besides the window's state
12 """
13 if is_valid():
14 return (0, 0) # Nothing needs to change: the empty window is already valid
15 
16 left = 0
17 min_len, best_left = float('inf'), -1 # length and start of the shortest valid window found so far
18 
19 for right in range(n):
20 # 1. Expand Right Frontier:
21 # Growing the window with the next index can only help reach a valid window, since add never removes anything the window already needed.
22 add(right)
23 
24 # 2. While Valid, Record And Shrink:
25 # A window starting at left that is already valid stays valid or better as it grows, so any longer window from the same left is worse; record it, then shrink from left since no longer window from this left can beat the one just recorded.
26 while is_valid():
27 if right - left + 1 < min_len:
28 min_len, best_left = right - left + 1, left
29 remove(left)
30 left += 1
31 
32 return (min_len, best_left)
Invariant: curr_sum is the sum of nums[left..right], and every window that starts before left has already had its shortest valid length recorded.

Grow the window by moving right. As soon as it is valid (curr_sum >= target), shrink it from left while it stays valid, recording its length each time.

Once a window starting at left is valid, any longer window from the same start is worse, so left can move on for good: neither pointer ever moves back, and no shorter window is skipped.

The trap is negative numbers: dropping nums[left] can then raise curr_sum, so shrinking can turn an invalid window valid again and the scan misses answers; Shortest Subarray with Sum at Least K (LC 862) allows negatives and needs prefix sums with a monotonic deque instead.

See it run

Shortest Window, step by step

left 0 · right - · curr_sum 0 · min_len -

left 0 · right 0 · curr_sum 2 · min_len -

left 0 · right 1 · curr_sum 5 · min_len -

left 0 · right 2 · curr_sum 6 · min_len -

left 0 · right 3 · curr_sum 8 · min_len -

left 1 · right 3 · curr_sum 6 · min_len 4

left 1 · right 4 · curr_sum 10 · min_len 4

left 2 · right 4 · curr_sum 7 · min_len 4

left 3 · right 4 · curr_sum 6 · min_len 3

left 3 · right 5 · curr_sum 9 · min_len 3

left 4 · right 5 · curr_sum 7 · min_len 3

left 5 · right 5 · curr_sum 3 · min_len 2

left 5 · right 5 · curr_sum 3 · min_len 2 · answer 2

Each box is one entry of nums, with its index above it. The window runs from left to right.

  • left the first index of the window
  • right the last index of the window
  • left, right a window of one entry
  • window an entry between left and right
  • removed the entry that left the window

Start: left = 0 and curr_sum = 0. Move right to grow the window until curr_sum reaches 7.

Add nums[0] = 2, so curr_sum is 2. That is below the target 7, so keep growing.

Add nums[1] = 3, so curr_sum is 5. That is below the target 7, so keep growing.

Add nums[2] = 1, so curr_sum is 6. That is below the target 7, so keep growing.

Add nums[3] = 2, so curr_sum is 8. That reaches the target, so shrink from left.

Record length 4 for nums[0..3], whose sum is 8. Remove nums[0] = 2, so curr_sum is 6 and the window is no longer valid, so it is not recorded.

Add nums[4] = 4, so curr_sum is 10. That reaches the target, so shrink from left.

Record length 4 for nums[1..4], whose sum is 10. Remove nums[1] = 3, so curr_sum is 7 and the window is still valid.

Record length 3 for nums[2..4], whose sum is 7. Remove nums[2] = 1, so curr_sum is 6 and the window is no longer valid, so it is not recorded.

Add nums[5] = 3, so curr_sum is 9. That reaches the target, so shrink from left.

Record length 3 for nums[3..5], whose sum is 9. Remove nums[3] = 2, so curr_sum is 7 and the window is still valid.

Record length 2 for nums[4..5], whose sum is 7. Remove nums[4] = 4, so curr_sum is 3 and the window is no longer valid, so it is not recorded.

Done: the shortest valid window has length 2, so return 2.

02
13
21
32
44
53
1 / 13
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Using the shrinking window when nums has negative numbers

    Mistake
    minSubArrayLen(k, nums)
    Fix
    shortest_subarray(nums, k)

    What happens: The loop shrinks by dropping nums[left], and it expects curr_sum to go down. A negative nums[left] makes curr_sum go up instead. So the sum can fall below target and then rise again as more values leave. The loop stops at the first fall and misses the shorter windows. Shortest Subarray with Sum at Least K (LC 862) allows negatives. It needs prefix sums with a monotonic deque, as in shortest_subarray.

    Example: Shortest Subarray with Sum at Least K (LC 862): nums = [1, -1, 5], k = 5
    Mistake: returns 3Fix: returns 1
  2. 2

    Gotcha 2: Recording the length after the shrinking loop

    Mistake
    while curr_sum >= target:
        curr_sum -= nums[left]
        left += 1
    min_len = min(min_len, right - left + 1)
    Fix
    while curr_sum >= target:
        min_len = min(min_len, right - left + 1)
        curr_sum -= nums[left]
        left += 1

    What happens: The shrinking loop stops when curr_sum is below target. So after the loop, the window is not valid. The mistake records the length of that invalid window. It even records a length on steps where no window was ever valid. The answer comes out too small.

    Example: nums = [2, 3, 1, 2, 4, 3], target = 7
    Mistake: returns 1Fix: returns 2
  3. 3

    Gotcha 3: Returning the inf placeholder when no window is valid

    Mistake
    return min_len
    Fix
    return 0 if min_len == float('inf') else min_len

    What happens: min_len starts at float('inf'), so any real length is smaller. When no window reaches target, min_len never changes. Minimum Size Subarray Sum (LC 209) asks for 0 in that case, not infinity.

    Example: nums = [1, 1], target = 5
    Mistake: returns infFix: returns 0
  4. 4

    Gotcha 4: Comparing whole count maps in Minimum Window Substring (LC 76)

    What happens: In Minimum Window Substring (LC 76), the window is valid when it holds every required character often enough. Comparing two whole count maps reads every character at every step. Keep one number, formed, instead. Add 1 to formed only when a required character's running count in the window equals its required count, checked with ==, never >=: >= would add 1 again on every extra duplicate past the quota (a 5th 'A' when only 2 are required), making formed climb on a character that is already satisfied while a character still short, like 'B' or 'C', never gets counted for it. Subtract 1 from formed when a required character's running count drops below its required count. The window is valid when formed == required. The answer is the same, and each step costs O(1).

Not this one if…
  • The question asks for the longest window that stays within a limit, as in Longest Substring Without Repeating Characters (LC 3): adding elements makes the window worse, not better. See Longest Window
  • The sum must be exactly k and values can be negative, as in Subarray Sum Equals K (LC 560): adding an element does not always move the window closer to valid. See Prefix Sum Map

Theory

The question we build

What we know

nums holds non-negative integers. Adding an element to a window never lowers its sum, so a window that is valid stays valid as it grows.

What the problem gives us

The input is nums and target. Return the length of the shortest contiguous subarray whose sum is at least target, or 0 if none exists.

The question we build

For each right, we ask: "is the sum of nums[left..right] at least target?" Once that window is valid, is any longer window starting at the same left worth recording? A longer window from the same left has a larger sum and a larger length, so it can never beat the one just found.

Narrowing down to the template

Which windows that end at right can still be the answer? Once a window starting at left is valid, every longer window from the same left is worse (same or larger sum, more length), so left can move on for good. So shrinking from left while the window stays valid never skips a shorter answer, and left never needs to move backward. That is exactly Shortest Window: grow with right, and while the window is valid, record its length and shrink from left.

Step 1: Every window

Check every window (left, right), summing it from scratch. Correct: the sum is recomputed for each window. States the contract in one listing: the shortest window whose sum is at least target.

Step 1: Every windowPython
1def min_sub_array_len_brute(target, nums):
2 n = len(nums)
3 best = float('inf')
4 for left in range(n):
5 total = 0
6 for right in range(left, n):
7 total += nums[right]
8 if total >= target:
9 best = min(best, right - left + 1)
10 break # Longer windows from this left are worse
11 return 0 if best == float('inf') else best

Cost: O(N^2) time, O(1) extra space.

Step 2: Grow and shrink

Once a window starting at left is valid, every longer window from the same left is worse. So grow with right, and while the window stays valid, record its length and shrink from left. On input with negative numbers, dropping nums[left] can raise curr_sum instead of lowering it, so a shrink can turn an invalid window valid again and the scan misses the true shortest window.

Step 2: Grow and shrinkPython
1def minSubArrayLen(target: int, nums: List[int]) -> int:
2 left = 0
3 curr_sum = 0 # sum of nums[left..right], the current window
4 min_len = float('inf')
5 
6 for right in range(len(nums)):
7 # 1. Expand Right Frontier:
8 # Growing the window with nums[right] can only help reach curr_sum >= target, since every value added only raises the sum.
9 curr_sum += nums[right]
10 
11 # 2. While Valid, Record And Shrink:
12 # A window starting at left that already reaches target stays at least as large as it grows, so any longer window from the same left is worse; record it, then shrink from left since no longer window from this left can beat the one just recorded.
13 while curr_sum >= target:
14 min_len = min(min_len, right - left + 1)
15 curr_sum -= nums[left]
16 left += 1
17 
18 return 0 if min_len == float('inf') else min_len

Cost: O(N) time, O(1) extra space.

Step 3: The named template

The loop does not change between problems. Only add, remove and is_valid change.

Step 3: The named templatePython
1+New line: from typing import Callable, Tuple
2
3+New line: def shortest_window(n: int, add: Callable[[int], None], remove: Callable[[int], None], is_valid: Callable[[], bool]) -> Tuple[float, int]:
4+New line: """Shortest Window: the shortest valid window [left, right] of indices 0..n-1, as (length, left).
5+New line: Returns (0, 0) when the empty window is already valid, and (float('inf'), -1) when no window is valid.
6+New line: Precondition: every longer window that holds a valid window is valid too.
7+New line: add(i) and remove(i) update the window's state for index i.
8+New line: Smallest Range Covering Elements from K Lists does not fit, because it measures a range of values, not a length.
9+New line: T = the cost of one add, remove or is_valid call.
10+New line: Time: O(N * T) | Space: O(1) besides the window's state
11+New line: """
12+New line: if is_valid():
13+New line: return (0, 0) # Nothing needs to change: the empty window is already valid
14
15 left = 0
16+New line: min_len, best_left = float('inf'), -1 # length and start of the shortest valid window found so far
17
18+New line: for right in range(n):
19 # 1. Expand Right Frontier:
20+New line: # Growing the window with the next index can only help reach a valid window, since add never removes anything the window already needed.
21+New line: add(right)
22
23 # 2. While Valid, Record And Shrink:
24+New line: # A window starting at left that is already valid stays valid or better as it grows, so any longer window from the same left is worse; record it, then shrink from left since no longer window from this left can beat the one just recorded.
25+New line: while is_valid():
26+New line: if right - left + 1 < min_len:
27+New line: min_len, best_left = right - left + 1, left
28+New line: remove(left)
29 left += 1
30
31+New line: return (min_len, best_left)

Cost: O(N * T), where T is the cost of one add, remove or is_valid call.

BeforeAfterWhat changed
curr_sum += nums[right]add(right)Expanding the window becomes add.
while curr_sum >= target:while is_valid():The sum check becomes is_valid.
curr_sum -= nums[left]remove(left)Contracting the window becomes remove.
min_len = min(min_len, right - left + 1)min_len, best_left = right - left + 1, left (when smaller)The template also remembers where the shortest window started.

Practice

Canonical LeetCode:Minimum Size Subarray Sum (LC 209)Minimum Window Substring (LC 76)Replace the Substring for Balanced String (LC 1234)Minimum Consecutive Cards to Pick Up (LC 2260)
Theory Context•Two Pointers & Sliding Window

Builds on Longest Window, which you haven’t finished yet.

P1MediumBuilt on Longest Window~4 min speedrun

Variable-Size Sliding Window (Minimum Window)

Built on
Longest WindowShrink the window while it is still valid and record its length each time, instead of shrinking only when it becomes invalid.
The problem

Minimum Size Subarray Sum (LC 209)(opens leetcode.com)

nums is an array of positive integers, and target is a positive integer. Among all contiguous subarrays whose sum reaches at least target, find the shortest one and return its length — or 0 if no subarray sums that high.

Example 1
Input:target = 7, nums = [2,3,1,2,4,3]
Output:2
The subarray [4,3] sums to 7, meeting the target with the fewest elements possible.

Shortest Window

left -> right -> (Expand to Validate, Shrink to Minimize)
Spot it when: You want the shortest contiguous subarray or substring that meets a condition, where adding elements only makes the condition easier to meet.
Shortest Window
Python
1from typing import Callable, List, Tuple
2 
3 
4def shortest_window(n: int, add: Callable[[int], None], remove: Callable[[int], None], is_valid: Callable[[], bool]) -> Tuple[float, int]:
5 """Shortest Window: the shortest valid window [left, right] of indices 0..n-1, as (length, left).
6 Returns (0, 0) when the empty window is already valid, and (float('inf'), -1) when no window is valid.
7 Precondition: every longer window that holds a valid window is valid too.
8 add(i) and remove(i) update the window's state for index i.
9 Smallest Range Covering Elements from K Lists does not fit, because it measures a range of values, not a length.
10 T = the cost of one add, remove or is_valid call.
11 Time: O(N * T) | Space: O(1) besides the window's state
12 """
13 if is_valid():
14 return (0, 0) # Nothing needs to change: the empty window is already valid
15 
16 left = 0
17 min_len, best_left = float('inf'), -1 # length and start of the shortest valid window found so far
18 
19 for right in range(n):
20 # 1. Expand Right Frontier:
21 # Growing the window with the next index can only help reach a valid window, since add never removes anything the window already needed.
22 add(right)
23 
24 # 2. While Valid, Record And Shrink:
25 # A window starting at left that is already valid stays valid or better as it grows, so any longer window from the same left is worse; record it, then shrink from left since no longer window from this left can beat the one just recorded.
26 while is_valid():
27 if right - left + 1 < min_len:
28 min_len, best_left = right - left + 1, left
29 remove(left)
30 left += 1
31 
32 return (min_len, best_left)
Invariant: curr_sum is the sum of nums[left..right], and every window that starts before left has already had its shortest valid length recorded.

Grow the window by moving right. As soon as it is valid (curr_sum >= target), shrink it from left while it stays valid, recording its length each time.

Once a window starting at left is valid, any longer window from the same start is worse, so left can move on for good: neither pointer ever moves back, and no shorter window is skipped.

The trap is negative numbers: dropping nums[left] can then raise curr_sum, so shrinking can turn an invalid window valid again and the scan misses answers; Shortest Subarray with Sum at Least K (LC 862) allows negatives and needs prefix sums with a monotonic deque instead.

See it run

Shortest Window, step by step

left 0 · right - · curr_sum 0 · min_len -

left 0 · right 0 · curr_sum 2 · min_len -

left 0 · right 1 · curr_sum 5 · min_len -

left 0 · right 2 · curr_sum 6 · min_len -

left 0 · right 3 · curr_sum 8 · min_len -

left 1 · right 3 · curr_sum 6 · min_len 4

left 1 · right 4 · curr_sum 10 · min_len 4

left 2 · right 4 · curr_sum 7 · min_len 4

left 3 · right 4 · curr_sum 6 · min_len 3

left 3 · right 5 · curr_sum 9 · min_len 3

left 4 · right 5 · curr_sum 7 · min_len 3

left 5 · right 5 · curr_sum 3 · min_len 2

left 5 · right 5 · curr_sum 3 · min_len 2 · answer 2

Each box is one entry of nums, with its index above it. The window runs from left to right.

  • left the first index of the window
  • right the last index of the window
  • left, right a window of one entry
  • window an entry between left and right
  • removed the entry that left the window

Start: left = 0 and curr_sum = 0. Move right to grow the window until curr_sum reaches 7.

Add nums[0] = 2, so curr_sum is 2. That is below the target 7, so keep growing.

Add nums[1] = 3, so curr_sum is 5. That is below the target 7, so keep growing.

Add nums[2] = 1, so curr_sum is 6. That is below the target 7, so keep growing.

Add nums[3] = 2, so curr_sum is 8. That reaches the target, so shrink from left.

Record length 4 for nums[0..3], whose sum is 8. Remove nums[0] = 2, so curr_sum is 6 and the window is no longer valid, so it is not recorded.

Add nums[4] = 4, so curr_sum is 10. That reaches the target, so shrink from left.

Record length 4 for nums[1..4], whose sum is 10. Remove nums[1] = 3, so curr_sum is 7 and the window is still valid.

Record length 3 for nums[2..4], whose sum is 7. Remove nums[2] = 1, so curr_sum is 6 and the window is no longer valid, so it is not recorded.

Add nums[5] = 3, so curr_sum is 9. That reaches the target, so shrink from left.

Record length 3 for nums[3..5], whose sum is 9. Remove nums[3] = 2, so curr_sum is 7 and the window is still valid.

Record length 2 for nums[4..5], whose sum is 7. Remove nums[4] = 4, so curr_sum is 3 and the window is no longer valid, so it is not recorded.

Done: the shortest valid window has length 2, so return 2.

02
13
21
32
44
53
1 / 13
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Using the shrinking window when nums has negative numbers

    Mistake
    minSubArrayLen(k, nums)
    Fix
    shortest_subarray(nums, k)

    What happens: The loop shrinks by dropping nums[left], and it expects curr_sum to go down. A negative nums[left] makes curr_sum go up instead. So the sum can fall below target and then rise again as more values leave. The loop stops at the first fall and misses the shorter windows. Shortest Subarray with Sum at Least K (LC 862) allows negatives. It needs prefix sums with a monotonic deque, as in shortest_subarray.

    Example: Shortest Subarray with Sum at Least K (LC 862): nums = [1, -1, 5], k = 5
    Mistake: returns 3Fix: returns 1
  2. 2

    Gotcha 2: Recording the length after the shrinking loop

    Mistake
    while curr_sum >= target:
        curr_sum -= nums[left]
        left += 1
    min_len = min(min_len, right - left + 1)
    Fix
    while curr_sum >= target:
        min_len = min(min_len, right - left + 1)
        curr_sum -= nums[left]
        left += 1

    What happens: The shrinking loop stops when curr_sum is below target. So after the loop, the window is not valid. The mistake records the length of that invalid window. It even records a length on steps where no window was ever valid. The answer comes out too small.

    Example: nums = [2, 3, 1, 2, 4, 3], target = 7
    Mistake: returns 1Fix: returns 2
  3. 3

    Gotcha 3: Returning the inf placeholder when no window is valid

    Mistake
    return min_len
    Fix
    return 0 if min_len == float('inf') else min_len

    What happens: min_len starts at float('inf'), so any real length is smaller. When no window reaches target, min_len never changes. Minimum Size Subarray Sum (LC 209) asks for 0 in that case, not infinity.

    Example: nums = [1, 1], target = 5
    Mistake: returns infFix: returns 0
  4. 4

    Gotcha 4: Comparing whole count maps in Minimum Window Substring (LC 76)

    What happens: In Minimum Window Substring (LC 76), the window is valid when it holds every required character often enough. Comparing two whole count maps reads every character at every step. Keep one number, formed, instead. Add 1 to formed only when a required character's running count in the window equals its required count, checked with ==, never >=: >= would add 1 again on every extra duplicate past the quota (a 5th 'A' when only 2 are required), making formed climb on a character that is already satisfied while a character still short, like 'B' or 'C', never gets counted for it. Subtract 1 from formed when a required character's running count drops below its required count. The window is valid when formed == required. The answer is the same, and each step costs O(1).

Not this one if…
  • The question asks for the longest window that stays within a limit, as in Longest Substring Without Repeating Characters (LC 3): adding elements makes the window worse, not better. See Longest Window
  • The sum must be exactly k and values can be negative, as in Subarray Sum Equals K (LC 560): adding an element does not always move the window closer to valid. See Prefix Sum Map

Theory

The question we build

What we know

nums holds non-negative integers. Adding an element to a window never lowers its sum, so a window that is valid stays valid as it grows.

What the problem gives us

The input is nums and target. Return the length of the shortest contiguous subarray whose sum is at least target, or 0 if none exists.

The question we build

For each right, we ask: "is the sum of nums[left..right] at least target?" Once that window is valid, is any longer window starting at the same left worth recording? A longer window from the same left has a larger sum and a larger length, so it can never beat the one just found.

Narrowing down to the template

Which windows that end at right can still be the answer? Once a window starting at left is valid, every longer window from the same left is worse (same or larger sum, more length), so left can move on for good. So shrinking from left while the window stays valid never skips a shorter answer, and left never needs to move backward. That is exactly Shortest Window: grow with right, and while the window is valid, record its length and shrink from left.

Step 1: Every window

Check every window (left, right), summing it from scratch. Correct: the sum is recomputed for each window. States the contract in one listing: the shortest window whose sum is at least target.

Step 1: Every windowPython
1def min_sub_array_len_brute(target, nums):
2 n = len(nums)
3 best = float('inf')
4 for left in range(n):
5 total = 0
6 for right in range(left, n):
7 total += nums[right]
8 if total >= target:
9 best = min(best, right - left + 1)
10 break # Longer windows from this left are worse
11 return 0 if best == float('inf') else best

Cost: O(N^2) time, O(1) extra space.

Step 2: Grow and shrink

Once a window starting at left is valid, every longer window from the same left is worse. So grow with right, and while the window stays valid, record its length and shrink from left. On input with negative numbers, dropping nums[left] can raise curr_sum instead of lowering it, so a shrink can turn an invalid window valid again and the scan misses the true shortest window.

Step 2: Grow and shrinkPython
1def minSubArrayLen(target: int, nums: List[int]) -> int:
2 left = 0
3 curr_sum = 0 # sum of nums[left..right], the current window
4 min_len = float('inf')
5 
6 for right in range(len(nums)):
7 # 1. Expand Right Frontier:
8 # Growing the window with nums[right] can only help reach curr_sum >= target, since every value added only raises the sum.
9 curr_sum += nums[right]
10 
11 # 2. While Valid, Record And Shrink:
12 # A window starting at left that already reaches target stays at least as large as it grows, so any longer window from the same left is worse; record it, then shrink from left since no longer window from this left can beat the one just recorded.
13 while curr_sum >= target:
14 min_len = min(min_len, right - left + 1)
15 curr_sum -= nums[left]
16 left += 1
17 
18 return 0 if min_len == float('inf') else min_len

Cost: O(N) time, O(1) extra space.

Step 3: The named template

The loop does not change between problems. Only add, remove and is_valid change.

Step 3: The named templatePython
1+New line: from typing import Callable, Tuple
2
3+New line: def shortest_window(n: int, add: Callable[[int], None], remove: Callable[[int], None], is_valid: Callable[[], bool]) -> Tuple[float, int]:
4+New line: """Shortest Window: the shortest valid window [left, right] of indices 0..n-1, as (length, left).
5+New line: Returns (0, 0) when the empty window is already valid, and (float('inf'), -1) when no window is valid.
6+New line: Precondition: every longer window that holds a valid window is valid too.
7+New line: add(i) and remove(i) update the window's state for index i.
8+New line: Smallest Range Covering Elements from K Lists does not fit, because it measures a range of values, not a length.
9+New line: T = the cost of one add, remove or is_valid call.
10+New line: Time: O(N * T) | Space: O(1) besides the window's state
11+New line: """
12+New line: if is_valid():
13+New line: return (0, 0) # Nothing needs to change: the empty window is already valid
14
15 left = 0
16+New line: min_len, best_left = float('inf'), -1 # length and start of the shortest valid window found so far
17
18+New line: for right in range(n):
19 # 1. Expand Right Frontier:
20+New line: # Growing the window with the next index can only help reach a valid window, since add never removes anything the window already needed.
21+New line: add(right)
22
23 # 2. While Valid, Record And Shrink:
24+New line: # A window starting at left that is already valid stays valid or better as it grows, so any longer window from the same left is worse; record it, then shrink from left since no longer window from this left can beat the one just recorded.
25+New line: while is_valid():
26+New line: if right - left + 1 < min_len:
27+New line: min_len, best_left = right - left + 1, left
28+New line: remove(left)
29 left += 1
30
31+New line: return (min_len, best_left)

Cost: O(N * T), where T is the cost of one add, remove or is_valid call.

BeforeAfterWhat changed
curr_sum += nums[right]add(right)Expanding the window becomes add.
while curr_sum >= target:while is_valid():The sum check becomes is_valid.
curr_sum -= nums[left]remove(left)Contracting the window becomes remove.
min_len = min(min_len, right - left + 1)min_len, best_left = right - left + 1, left (when smaller)The template also remembers where the shortest window started.

Practice

Canonical LeetCode:Minimum Size Subarray Sum (LC 209)Minimum Window Substring (LC 76)Replace the Substring for Balanced String (LC 1234)Minimum Consecutive Cards to Pick Up (LC 2260)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•