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.

P3HardBuilt on Longest Window~4 min speedrun

Exactly K = At Most K − At Most (K−1)

Built on
Longest WindowCount the windows that end at each right end with an at-most count, then subtract two at-most counts to get an exact count.

Binary Subarrays With Sum (LC 930)(opens leetcode.com)

You get an array nums in which every element is 0 or 1, and a whole number goal. A subarray is a run of one or more neighbouring elements of nums, taken without gaps. Count the subarrays whose elements add up to exactly goal, and return that number.

Subarrays are told apart by where they start and end: two runs at different positions both count, even if they hold the same values. When goal is 0, the subarrays made only of zeros are the ones that count.

Example 1
Input:nums = [1,0,1,0,1], goal = 2
Output:4
The runs with exactly two 1s are indices 0 to 2 (1,0,1), 0 to 3 (1,0,1,0), 1 to 4 (0,1,0,1) and 2 to 4 (1,0,1).
1001120314111

At-Most Trick

exactly K âž” atMost(K) - atMost(K - 1) âž” two shrinking windows
Spot it when: You must count the subarrays that have exactly K of something (a sum of 0s and 1s, odd numbers, different values), and adding an element never lowers that amount, so "at most K" is a window that only has to shrink from the left.
At-Most Trick
Python
1from typing import Callable
2 
3 
4def exactly(at_most: Callable[[int], int], k: int) -> int:
5 """At-Most Trick: how many items measure exactly k, counted as at_most(k) - at_most(k - 1).
6 at_most(limit) counts the items that measure at most limit, for example the subarrays with at most limit distinct values.
7 Precondition: every measure is a whole number, so an item that measures at most k measures exactly k or at most k - 1, never both.
8 at_most(-1) must return 0.
9 A range such as Number of Subarrays with Bounded Maximum is at_most(right) - at_most(left - 1), outside this template.
10 T = the time of one at_most call. S = the space of one at_most call.
11 Time: O(T) | Space: O(S)
12 """
13 return at_most(k) - at_most(k - 1) # At most k minus at most k - 1 leaves exactly k
Invariant: After each right, nums[left..right] is the longest window ending at right whose sum is at most limit, so count holds how many subarrays with sum at most limit end at or before right.

"Exactly goal" can't be kept by one window: zeros can join or leave without changing the sum, so there is no single left edge to move, but "at most limit" can, and at_most(limit) adds right - left + 1 for every right (each start from left to right fits), so numSubarraysWithSum returns at_most(goal) - at_most(goal - 1). The subtraction is exact because a subarray with sum at most goal has sum goal or sum at most goal - 1, never both; and the window is right because the values are never negative: if nums[left..right] fits, every shorter window ending at right fits too, so the while window > limit loop only ever moves left forward. The trap is goal = 0: at_most(goal - 1) becomes at_most(-1), which must return 0 (if limit < 0: return 0), or the while loop never finds a sum small enough and walks left past right and off the end of nums.

See it run

Binary Subarrays With Sum: at_most(2) - at_most(1), nums = [1, 0, 1, 0, 1]

limit 2 · window 0 · count 0

limit 2 · window 1 · count 1

limit 2 · window 1 · count 3

limit 2 · window 2 · count 6

limit 2 · window 2 · count 10

limit 2 · window 3 · count 10

limit 2 · window 2 · count 10

limit 2 · window 2 · count 14

limit 1 · window 0 · count 0 · at_most(2) 14

limit 1 · window 1 · count 1 · at_most(2) 14

limit 1 · window 1 · count 3 · at_most(2) 14

limit 1 · window 2 · count 3 · at_most(2) 14

limit 1 · window 1 · count 3 · at_most(2) 14

limit 1 · window 1 · count 5 · at_most(2) 14

limit 1 · window 1 · count 8 · at_most(2) 14

limit 1 · window 2 · count 8 · at_most(2) 14

limit 1 · window 2 · count 8 · at_most(2) 14

limit 1 · window 1 · count 8 · at_most(2) 14

limit 1 · window 1 · count 10 · at_most(2) 14

at_most(2) 14 · at_most(1) 10 · answer 4

The top row is nums. The bottom row shows what each right added to count. The board runs at_most twice, and the second run starts again from the left.

  • left the first index of the window
  • right the index that was just added to the window
  • left, right left and right are at the same index
  • window an index between left and right
  • added the amount that this right added to count

Call at_most(2). Here left, window and count all start at 0.

window += nums[0] makes window 1, at most 2. So count += 0 - 0 + 1 = 1.

window += nums[1] makes window 1, at most 2. So count += 1 - 0 + 1 = 2.

window += nums[2] makes window 2, at most 2. So count += 2 - 0 + 1 = 3.

window += nums[3] makes window 2, at most 2. So count += 3 - 0 + 1 = 4.

window += nums[4] makes window 3, above 2. The window must shrink from the left.

Dropping nums[0] = 1 leaves window 2, which is at most 2. Now left is 1.

Every start from 1 to 4 fits, so count += 4 - 1 + 1 = 4.

at_most(2) returned 14. Call at_most(1), and left, window and count start at 0 again.

window += nums[0] makes window 1, at most 1. So count += 0 - 0 + 1 = 1.

window += nums[1] makes window 1, at most 1. So count += 1 - 0 + 1 = 2.

window += nums[2] makes window 2, above 1. The window must shrink from the left.

Dropping nums[0] = 1 leaves window 1, which is at most 1. Now left is 1.

Every start from 1 to 2 fits, so count += 2 - 1 + 1 = 2.

window += nums[3] makes window 1, at most 1. So count += 3 - 1 + 1 = 3.

window += nums[4] makes window 2, above 1. The window must shrink from the left.

Dropping nums[1] = 0 leaves window 2, still above 1. A while loop drops again; an if would stop here.

Dropping nums[2] = 1 leaves window 1, which is at most 1. Now left is 3.

Every start from 3 to 4 fits, so count += 4 - 3 + 1 = 2.

14 - 10 = 4. These are the subarrays whose sum is exactly 2.

nums
01
10
21
30
41
added
1 / 20
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Leaving out if limit < 0: return 0

    Mistake
    pass  # no guard
    Fix
    if limit < 0:
        return 0

    What happens: With goal = 0, the second call is at_most(-1). No window has a sum below 0, so while window > limit is always True. The loop moves left past right and off the end of nums. Python then raises an IndexError.

    Example: nums = [0, 0, 0, 0, 0], goal = 0
    Mistake: raises IndexErrorFix: returns 15
  2. 2

    Gotcha 2: Adding 1 per step instead of right - left + 1

    Mistake
    count += 1
    Fix
    count += right - left + 1

    What happens: For each right, every start from left to right gives a subarray with sum at most limit. There are right - left + 1 such starts: the subarray nums[s..right] is valid for every s from left to right, because if the longest window ending at right fits under limit, every shorter window ending at right fits too. Adding 1 counts only the longest of them, so it misses every other valid start.

    Example: nums = [0, 0, 0, 0, 0], goal = 0
    Mistake: returns 5Fix: returns 15
  3. 3

    Gotcha 3: Shrinking with if instead of while

    Mistake
    if window > limit:
    Fix
    while window > limit:

    What happens: One step drops only nums[left], and that value may be 0. Then the sum is still above limit. The window is counted while its sum is too large.

    Example: nums = [1, 0, 1, 0, 1], goal = 2
    Mistake: returns 3Fix: returns 4
  4. 4

    Gotcha 4: Passing the raw values instead of x % 2

    Mistake
    numSubarraysWithSum(nums, k)
    Fix
    numSubarraysWithSum([x % 2 for x in nums], k)

    What happens: Count Number of Nice Subarrays (LC 1248) counts the subarrays with exactly k odd numbers. With the raw values, the window adds up the numbers themselves. Map each odd value to 1 and each even value to 0 first. Then the window sum is the number of odd values.

    Example: Count Number of Nice Subarrays (LC 1248), Example 3: nums = [2, 2, 2, 1, 2, 2, 1, 2, 2, 2], k = 2
    Mistake: returns 8Fix: returns 16
Not this one if…
  • The question asks for the longest window within "at most K", as in Longest Substring with At Most K Distinct Characters (LC 340): one window answers it, with no count of every subarray and no subtraction. See Longest Window
  • Values can be negative, as in Subarray Sum Equals K (LC 560): adding an element can lower the sum, so "sum at most K" is not a window that only shrinks from the left. See Prefix Sum Map

Deep Dive

Why a single window cannot count an exact sum

The Longest Window card moves left forward only when the window breaks a rule, and a longer window never becomes valid again after it breaks. A rule of the form "sum at most limit" has this property when no value is negative. Adding nums[right] can only raise window, and dropping nums[left] can only lower it.

A rule of the form "sum exactly goal" has no such property. Take nums = [1, 0, 1, 0, 1] and goal = 2. The window [1, 0, 1] has sum 2. Add the next 0, and the sum is still 2, so the window stays valid. Drop the first 1 from [1, 0, 1, 0], and the sum falls to 1, so it becomes invalid. Zeros can join or leave without changing the sum. So there is no single left that you can move forward, and no way to say that a window is the longest one.

The fix is to count a different set. at_most(limit) counts the subarrays whose sum is at most limit. That count does have a window: when window > limit, move left forward until the sum fits.

Tracing at_most(2) - at_most(1) on [1, 0, 1, 0, 1]

at_most(limit) adds right - left + 1 to count at every right. The window nums[left..right] is the longest window ending at right with a sum at most limit. Every shorter window that ends at right has a sum that is not larger, so it fits too. The starts left, left + 1, up to right give exactly right - left + 1 subarrays.

With limit = 2, the added amounts for right = 0 to 4 are 1, 2, 3, 4 and 4, so at_most(2) returns 14. At right = 4, the window sum is 3, so one drop of nums[0] = 1 brings it back to 2, and left becomes 1.

With limit = 1, the added amounts are 1, 2, 2, 3 and 2, so at_most(1) returns 10. At right = 4, the sum is 2. Dropping nums[1] = 0 leaves the sum at 2, so the loop must drop again. This is why the shrink step is a while. An if would count a window whose sum is above limit.

The answer is 14 - 10 = 4. This matches Binary Subarrays With Sum (LC 930), Example 1.

Why the subtraction is exact, and the goal = 0 trap

Every subarray with sum at most goal has a sum that is goal, or a sum that is at most goal - 1. It cannot be both. So at_most(goal) counts the exact-goal subarrays plus the at-most-goal - 1 subarrays, and at_most(goal - 1) counts only the second group. The difference leaves the exact-goal subarrays.

This needs whole-number measures that never fall when an element is added. A sum of values that are never negative works. A count of different values works too, because adding an element can only keep that count or raise it. A sum that includes negative values does not work. There, dropping nums[left] can raise the sum, so the while loop is not a valid rule. Subarray Sum Equals K (LC 560) needs a prefix-sum lookup instead.

The trap is goal = 0. The second call becomes at_most(-1). No subarray of 0s and 1s has a negative sum, so window > limit is always true. The while loop keeps moving left past right and off the end of nums. The guard if limit < 0: return 0 stops this. For nums = [0, 0, 0, 0, 0] and goal = 0, the correct answer is 15 (5 + 4 + 3 + 2 + 1), and the unguarded code raises IndexError.

Using the template on other problems, and the cost

The template exactly(at_most, k) = at_most(k) - at_most(k - 1) does not depend on the window. Only at_most changes.

For Subarrays with K Different Integers (LC 992), at_most(limit) keeps a count of each value in the window and shrinks while the number of different values is above limit. For Count Number of Nice Subarrays (LC 1248), map each number to x % 2 first. Then the sum of the window is the number of odd values, and the code is the same as for Binary Subarrays With Sum (LC 930).

Each at_most call moves right and left forward at most N times each, so one call costs O(N) time. Two calls cost O(N) time. The space is O(1) for the sum version. The distinct-values version stores a count for each value in the window, which costs O(N) space in the worst case.

A range such as Number of Subarrays with Bounded Maximum (LC 795) uses at_most(right) - at_most(left - 1) in a different form. The card's template covers exact values only.

60-Second Pitch

"One window cannot hold an exact sum, because zeros can join or leave without changing it. So I count something a window can hold: the subarrays with a sum at most limit. For each right, I add the new value and shrink from the left while the sum is above limit. Every start from left to right gives a valid subarray, so I add right - left + 1 to count. A subarray with a sum at most goal has a sum of exactly goal or at most goal - 1, never both. So the answer is at_most(goal) - at_most(goal - 1). I return 0 for a negative limit, or goal = 0 breaks the loop. Each call is O(N) time and O(1) space, so the whole solution is O(N) time and O(1) space."

Common Questions

Why not keep one window and count only the windows with an exact sum?

Zeros can join or leave the window without changing its sum. So the window with sum goal has no single left edge. Each right end has a range of valid left ends, and one pointer cannot move through it in one pass. The at-most version has one left end for each right end, so it works.

What is the time and space complexity?

Each at_most call moves right once over nums and left at most once over nums, so it is O(N) time. Two calls are still O(N) time. The sum version needs O(1) space. The version for different values stores one count for each value in the window, which is O(N) space in the worst case.

Does this work when the array has negative numbers?

No. Dropping nums[left] from the window can raise the sum, so "sum at most limit" is not a window that only shrinks. Use prefix sums and a hash map, as in Subarray Sum Equals K (LC 560).

How do you adapt it to count subarrays with exactly K different integers?

Replace the sum with a count of each value in the window. Shrink while the number of different values is above limit. The answer for Subarrays with K Different Integers (LC 992) is again at_most(k) - at_most(k - 1). The measure never falls when an element is added, so the same argument holds.

Is there a one-pass version?

Yes, for some problems. A second pointer can track the start of the earliest window that still has exactly goal, but the bookkeeping is easy to get wrong. The two-call version is shorter to write and easier to check, so it is the usual choice in an interview.

SWE Reasoning

Spot it

  • The question counts subarrays with an exact value: exactly goal as a sum, exactly K odd numbers, or exactly K different values.
  • Adding an element never lowers the measure, so "at most K" is a window that only shrinks from the left.
  • A single window fails for "exactly K", because a zero, an even number or a repeated value changes nothing about the measure.
  • The answer is a count of subarrays, not the length of the longest one.

The invariant, step by step

  1. at_most(limit) returns 0 at once when limit < 0. No subarray has a measure below zero.
  2. For each right, window += nums[right] adds the new value to the sum of nums[left..right].
  3. While window > limit, window -= nums[left] and left += 1 drop the oldest value. The window ends as the longest one ending at right with a sum at most limit.
  4. count += right - left + 1 counts every start from left to right. Each of them gives a subarray ending at right with a sum at most limit.
  5. After the last right, count holds every subarray with a sum at most limit.
  6. at_most(goal) - at_most(goal - 1) removes the subarrays with a sum at most goal - 1 from the subarrays with a sum at most goal. Only the subarrays with a sum of exactly goal remain.

Traps

  • Leaving out if limit < 0: return 0. With goal = 0, at_most(-1) never finds a small enough sum, and left runs off the end of nums.
  • Adding 1 per step. count += 1 counts only the longest window at each right. The answer for five zeros and goal = 0 is 5 instead of 15.
  • Shrinking with if instead of while. One drop may remove a zero, so the sum stays above limit and the window is counted while it is too large.
  • Using the template when values can be negative. The window rule needs a measure that never falls when an element is added.
  • Forgetting x % 2 for odd numbers. Without it, the window adds up the numbers themselves, not the count of odd numbers.

Practice

Canonical LeetCode:Subarrays with K Different Integers (LC 992)Binary Subarrays With Sum (LC 930)Count Number of Nice Subarrays (LC 1248)Number of Subarrays with Bounded Maximum (LC 795)
Theory Context•Two Pointers & Sliding Window

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

P3HardBuilt on Longest Window~4 min speedrun

Exactly K = At Most K − At Most (K−1)

Built on
Longest WindowCount the windows that end at each right end with an at-most count, then subtract two at-most counts to get an exact count.

Binary Subarrays With Sum (LC 930)(opens leetcode.com)

You get an array nums in which every element is 0 or 1, and a whole number goal. A subarray is a run of one or more neighbouring elements of nums, taken without gaps. Count the subarrays whose elements add up to exactly goal, and return that number.

Subarrays are told apart by where they start and end: two runs at different positions both count, even if they hold the same values. When goal is 0, the subarrays made only of zeros are the ones that count.

Example 1
Input:nums = [1,0,1,0,1], goal = 2
Output:4
The runs with exactly two 1s are indices 0 to 2 (1,0,1), 0 to 3 (1,0,1,0), 1 to 4 (0,1,0,1) and 2 to 4 (1,0,1).
1001120314111

At-Most Trick

exactly K âž” atMost(K) - atMost(K - 1) âž” two shrinking windows
Spot it when: You must count the subarrays that have exactly K of something (a sum of 0s and 1s, odd numbers, different values), and adding an element never lowers that amount, so "at most K" is a window that only has to shrink from the left.
At-Most Trick
Python
1from typing import Callable
2 
3 
4def exactly(at_most: Callable[[int], int], k: int) -> int:
5 """At-Most Trick: how many items measure exactly k, counted as at_most(k) - at_most(k - 1).
6 at_most(limit) counts the items that measure at most limit, for example the subarrays with at most limit distinct values.
7 Precondition: every measure is a whole number, so an item that measures at most k measures exactly k or at most k - 1, never both.
8 at_most(-1) must return 0.
9 A range such as Number of Subarrays with Bounded Maximum is at_most(right) - at_most(left - 1), outside this template.
10 T = the time of one at_most call. S = the space of one at_most call.
11 Time: O(T) | Space: O(S)
12 """
13 return at_most(k) - at_most(k - 1) # At most k minus at most k - 1 leaves exactly k
Invariant: After each right, nums[left..right] is the longest window ending at right whose sum is at most limit, so count holds how many subarrays with sum at most limit end at or before right.

"Exactly goal" can't be kept by one window: zeros can join or leave without changing the sum, so there is no single left edge to move, but "at most limit" can, and at_most(limit) adds right - left + 1 for every right (each start from left to right fits), so numSubarraysWithSum returns at_most(goal) - at_most(goal - 1). The subtraction is exact because a subarray with sum at most goal has sum goal or sum at most goal - 1, never both; and the window is right because the values are never negative: if nums[left..right] fits, every shorter window ending at right fits too, so the while window > limit loop only ever moves left forward. The trap is goal = 0: at_most(goal - 1) becomes at_most(-1), which must return 0 (if limit < 0: return 0), or the while loop never finds a sum small enough and walks left past right and off the end of nums.

See it run

Binary Subarrays With Sum: at_most(2) - at_most(1), nums = [1, 0, 1, 0, 1]

limit 2 · window 0 · count 0

limit 2 · window 1 · count 1

limit 2 · window 1 · count 3

limit 2 · window 2 · count 6

limit 2 · window 2 · count 10

limit 2 · window 3 · count 10

limit 2 · window 2 · count 10

limit 2 · window 2 · count 14

limit 1 · window 0 · count 0 · at_most(2) 14

limit 1 · window 1 · count 1 · at_most(2) 14

limit 1 · window 1 · count 3 · at_most(2) 14

limit 1 · window 2 · count 3 · at_most(2) 14

limit 1 · window 1 · count 3 · at_most(2) 14

limit 1 · window 1 · count 5 · at_most(2) 14

limit 1 · window 1 · count 8 · at_most(2) 14

limit 1 · window 2 · count 8 · at_most(2) 14

limit 1 · window 2 · count 8 · at_most(2) 14

limit 1 · window 1 · count 8 · at_most(2) 14

limit 1 · window 1 · count 10 · at_most(2) 14

at_most(2) 14 · at_most(1) 10 · answer 4

The top row is nums. The bottom row shows what each right added to count. The board runs at_most twice, and the second run starts again from the left.

  • left the first index of the window
  • right the index that was just added to the window
  • left, right left and right are at the same index
  • window an index between left and right
  • added the amount that this right added to count

Call at_most(2). Here left, window and count all start at 0.

window += nums[0] makes window 1, at most 2. So count += 0 - 0 + 1 = 1.

window += nums[1] makes window 1, at most 2. So count += 1 - 0 + 1 = 2.

window += nums[2] makes window 2, at most 2. So count += 2 - 0 + 1 = 3.

window += nums[3] makes window 2, at most 2. So count += 3 - 0 + 1 = 4.

window += nums[4] makes window 3, above 2. The window must shrink from the left.

Dropping nums[0] = 1 leaves window 2, which is at most 2. Now left is 1.

Every start from 1 to 4 fits, so count += 4 - 1 + 1 = 4.

at_most(2) returned 14. Call at_most(1), and left, window and count start at 0 again.

window += nums[0] makes window 1, at most 1. So count += 0 - 0 + 1 = 1.

window += nums[1] makes window 1, at most 1. So count += 1 - 0 + 1 = 2.

window += nums[2] makes window 2, above 1. The window must shrink from the left.

Dropping nums[0] = 1 leaves window 1, which is at most 1. Now left is 1.

Every start from 1 to 2 fits, so count += 2 - 1 + 1 = 2.

window += nums[3] makes window 1, at most 1. So count += 3 - 1 + 1 = 3.

window += nums[4] makes window 2, above 1. The window must shrink from the left.

Dropping nums[1] = 0 leaves window 2, still above 1. A while loop drops again; an if would stop here.

Dropping nums[2] = 1 leaves window 1, which is at most 1. Now left is 3.

Every start from 3 to 4 fits, so count += 4 - 3 + 1 = 2.

14 - 10 = 4. These are the subarrays whose sum is exactly 2.

nums
01
10
21
30
41
added
1 / 20
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Leaving out if limit < 0: return 0

    Mistake
    pass  # no guard
    Fix
    if limit < 0:
        return 0

    What happens: With goal = 0, the second call is at_most(-1). No window has a sum below 0, so while window > limit is always True. The loop moves left past right and off the end of nums. Python then raises an IndexError.

    Example: nums = [0, 0, 0, 0, 0], goal = 0
    Mistake: raises IndexErrorFix: returns 15
  2. 2

    Gotcha 2: Adding 1 per step instead of right - left + 1

    Mistake
    count += 1
    Fix
    count += right - left + 1

    What happens: For each right, every start from left to right gives a subarray with sum at most limit. There are right - left + 1 such starts: the subarray nums[s..right] is valid for every s from left to right, because if the longest window ending at right fits under limit, every shorter window ending at right fits too. Adding 1 counts only the longest of them, so it misses every other valid start.

    Example: nums = [0, 0, 0, 0, 0], goal = 0
    Mistake: returns 5Fix: returns 15
  3. 3

    Gotcha 3: Shrinking with if instead of while

    Mistake
    if window > limit:
    Fix
    while window > limit:

    What happens: One step drops only nums[left], and that value may be 0. Then the sum is still above limit. The window is counted while its sum is too large.

    Example: nums = [1, 0, 1, 0, 1], goal = 2
    Mistake: returns 3Fix: returns 4
  4. 4

    Gotcha 4: Passing the raw values instead of x % 2

    Mistake
    numSubarraysWithSum(nums, k)
    Fix
    numSubarraysWithSum([x % 2 for x in nums], k)

    What happens: Count Number of Nice Subarrays (LC 1248) counts the subarrays with exactly k odd numbers. With the raw values, the window adds up the numbers themselves. Map each odd value to 1 and each even value to 0 first. Then the window sum is the number of odd values.

    Example: Count Number of Nice Subarrays (LC 1248), Example 3: nums = [2, 2, 2, 1, 2, 2, 1, 2, 2, 2], k = 2
    Mistake: returns 8Fix: returns 16
Not this one if…
  • The question asks for the longest window within "at most K", as in Longest Substring with At Most K Distinct Characters (LC 340): one window answers it, with no count of every subarray and no subtraction. See Longest Window
  • Values can be negative, as in Subarray Sum Equals K (LC 560): adding an element can lower the sum, so "sum at most K" is not a window that only shrinks from the left. See Prefix Sum Map

Deep Dive

Why a single window cannot count an exact sum

The Longest Window card moves left forward only when the window breaks a rule, and a longer window never becomes valid again after it breaks. A rule of the form "sum at most limit" has this property when no value is negative. Adding nums[right] can only raise window, and dropping nums[left] can only lower it.

A rule of the form "sum exactly goal" has no such property. Take nums = [1, 0, 1, 0, 1] and goal = 2. The window [1, 0, 1] has sum 2. Add the next 0, and the sum is still 2, so the window stays valid. Drop the first 1 from [1, 0, 1, 0], and the sum falls to 1, so it becomes invalid. Zeros can join or leave without changing the sum. So there is no single left that you can move forward, and no way to say that a window is the longest one.

The fix is to count a different set. at_most(limit) counts the subarrays whose sum is at most limit. That count does have a window: when window > limit, move left forward until the sum fits.

Tracing at_most(2) - at_most(1) on [1, 0, 1, 0, 1]

at_most(limit) adds right - left + 1 to count at every right. The window nums[left..right] is the longest window ending at right with a sum at most limit. Every shorter window that ends at right has a sum that is not larger, so it fits too. The starts left, left + 1, up to right give exactly right - left + 1 subarrays.

With limit = 2, the added amounts for right = 0 to 4 are 1, 2, 3, 4 and 4, so at_most(2) returns 14. At right = 4, the window sum is 3, so one drop of nums[0] = 1 brings it back to 2, and left becomes 1.

With limit = 1, the added amounts are 1, 2, 2, 3 and 2, so at_most(1) returns 10. At right = 4, the sum is 2. Dropping nums[1] = 0 leaves the sum at 2, so the loop must drop again. This is why the shrink step is a while. An if would count a window whose sum is above limit.

The answer is 14 - 10 = 4. This matches Binary Subarrays With Sum (LC 930), Example 1.

Why the subtraction is exact, and the goal = 0 trap

Every subarray with sum at most goal has a sum that is goal, or a sum that is at most goal - 1. It cannot be both. So at_most(goal) counts the exact-goal subarrays plus the at-most-goal - 1 subarrays, and at_most(goal - 1) counts only the second group. The difference leaves the exact-goal subarrays.

This needs whole-number measures that never fall when an element is added. A sum of values that are never negative works. A count of different values works too, because adding an element can only keep that count or raise it. A sum that includes negative values does not work. There, dropping nums[left] can raise the sum, so the while loop is not a valid rule. Subarray Sum Equals K (LC 560) needs a prefix-sum lookup instead.

The trap is goal = 0. The second call becomes at_most(-1). No subarray of 0s and 1s has a negative sum, so window > limit is always true. The while loop keeps moving left past right and off the end of nums. The guard if limit < 0: return 0 stops this. For nums = [0, 0, 0, 0, 0] and goal = 0, the correct answer is 15 (5 + 4 + 3 + 2 + 1), and the unguarded code raises IndexError.

Using the template on other problems, and the cost

The template exactly(at_most, k) = at_most(k) - at_most(k - 1) does not depend on the window. Only at_most changes.

For Subarrays with K Different Integers (LC 992), at_most(limit) keeps a count of each value in the window and shrinks while the number of different values is above limit. For Count Number of Nice Subarrays (LC 1248), map each number to x % 2 first. Then the sum of the window is the number of odd values, and the code is the same as for Binary Subarrays With Sum (LC 930).

Each at_most call moves right and left forward at most N times each, so one call costs O(N) time. Two calls cost O(N) time. The space is O(1) for the sum version. The distinct-values version stores a count for each value in the window, which costs O(N) space in the worst case.

A range such as Number of Subarrays with Bounded Maximum (LC 795) uses at_most(right) - at_most(left - 1) in a different form. The card's template covers exact values only.

60-Second Pitch

"One window cannot hold an exact sum, because zeros can join or leave without changing it. So I count something a window can hold: the subarrays with a sum at most limit. For each right, I add the new value and shrink from the left while the sum is above limit. Every start from left to right gives a valid subarray, so I add right - left + 1 to count. A subarray with a sum at most goal has a sum of exactly goal or at most goal - 1, never both. So the answer is at_most(goal) - at_most(goal - 1). I return 0 for a negative limit, or goal = 0 breaks the loop. Each call is O(N) time and O(1) space, so the whole solution is O(N) time and O(1) space."

Common Questions

Why not keep one window and count only the windows with an exact sum?

Zeros can join or leave the window without changing its sum. So the window with sum goal has no single left edge. Each right end has a range of valid left ends, and one pointer cannot move through it in one pass. The at-most version has one left end for each right end, so it works.

What is the time and space complexity?

Each at_most call moves right once over nums and left at most once over nums, so it is O(N) time. Two calls are still O(N) time. The sum version needs O(1) space. The version for different values stores one count for each value in the window, which is O(N) space in the worst case.

Does this work when the array has negative numbers?

No. Dropping nums[left] from the window can raise the sum, so "sum at most limit" is not a window that only shrinks. Use prefix sums and a hash map, as in Subarray Sum Equals K (LC 560).

How do you adapt it to count subarrays with exactly K different integers?

Replace the sum with a count of each value in the window. Shrink while the number of different values is above limit. The answer for Subarrays with K Different Integers (LC 992) is again at_most(k) - at_most(k - 1). The measure never falls when an element is added, so the same argument holds.

Is there a one-pass version?

Yes, for some problems. A second pointer can track the start of the earliest window that still has exactly goal, but the bookkeeping is easy to get wrong. The two-call version is shorter to write and easier to check, so it is the usual choice in an interview.

SWE Reasoning

Spot it

  • The question counts subarrays with an exact value: exactly goal as a sum, exactly K odd numbers, or exactly K different values.
  • Adding an element never lowers the measure, so "at most K" is a window that only shrinks from the left.
  • A single window fails for "exactly K", because a zero, an even number or a repeated value changes nothing about the measure.
  • The answer is a count of subarrays, not the length of the longest one.

The invariant, step by step

  1. at_most(limit) returns 0 at once when limit < 0. No subarray has a measure below zero.
  2. For each right, window += nums[right] adds the new value to the sum of nums[left..right].
  3. While window > limit, window -= nums[left] and left += 1 drop the oldest value. The window ends as the longest one ending at right with a sum at most limit.
  4. count += right - left + 1 counts every start from left to right. Each of them gives a subarray ending at right with a sum at most limit.
  5. After the last right, count holds every subarray with a sum at most limit.
  6. at_most(goal) - at_most(goal - 1) removes the subarrays with a sum at most goal - 1 from the subarrays with a sum at most goal. Only the subarrays with a sum of exactly goal remain.

Traps

  • Leaving out if limit < 0: return 0. With goal = 0, at_most(-1) never finds a small enough sum, and left runs off the end of nums.
  • Adding 1 per step. count += 1 counts only the longest window at each right. The answer for five zeros and goal = 0 is 5 instead of 15.
  • Shrinking with if instead of while. One drop may remove a zero, so the sum stays above limit and the window is counted while it is too large.
  • Using the template when values can be negative. The window rule needs a measure that never falls when an element is added.
  • Forgetting x % 2 for odd numbers. Without it, the window adds up the numbers themselves, not the count of odd numbers.

Practice

Canonical LeetCode:Subarrays with K Different Integers (LC 992)Binary Subarrays With Sum (LC 930)Count Number of Nice Subarrays (LC 1248)Number of Subarrays with Bounded Maximum (LC 795)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•