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
P2EasyMental model~3 min speedrun

Prefix Sum Array

Grows into
Prefix-Suffix ScanPrefix Sum MapDifference Array
Unlocks
Fenwick TreeSegment Tree

Range Sum Query - Immutable (LC 303)(opens leetcode.com)

An integer array nums is given once, and it never changes. After that, many questions arrive. Each question names two indices, left and right, with left <= right, and asks for the total of the elements from index left to index right, both ends included. That total is the sum below.

nums[left] + nums[left + 1] + ... + nums[right]

Your class is NumArray. Its constructor, NumArray(nums), receives the array. Its method sumRange(left, right) answers one question and returns that total as an integer.

Example 1
Input:["NumArray", "sumRange", "sumRange", "sumRange"] [[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]]
Output:[null, 1, -1, -3]
NumArray([-2, 0, 3, -5, 2, -1]) receives the array and returns null. sumRange(0, 2) is -2 + 0 + 3 = 1. sumRange(2, 5) is 3 + (-5) + 2 + (-1) = -1. sumRange(0, 5) is the whole array: -2 + 0 + 3 + (-5) + 2 + (-1) = -3.
-200132-5324-15leftright

Prefix Sums

nums ➔ prefix with n + 1 slots, prefix[0] = 0 ➔ prefix[right + 1] - prefix[left]
Spot it when: The array does not change, and you must answer many questions about the sum, count or XOR of a given range left..right. Or you need the running total at each position, counted from the start.
Prefix Sums
Python
1class NumArray:
2 """Range Sum Query - Immutable: build the prefix array once, then answer each range with one subtraction.
3 Time: O(N) to build, O(1) per sumRange | Space: O(N)
4 """
5 
6 def __init__(self, nums: list[int]) -> None:
7 # prefix[i]: sum of the first i elements of nums; prefix[0] = 0, the sum of no elements
8 self.prefix = [0] * (len(nums) + 1)
9 # 1. Build The Prefix Array Once:
10 # Each next slot adds one more element to the previous running total, so prefix[i + 1] is always one element ahead of prefix[i].
11 for i, num in enumerate(nums):
12 self.prefix[i + 1] = self.prefix[i] + num
13 
14 def sumRange(self, left: int, right: int) -> int:
15 # 1. Subtract The Two Running Totals:
16 # The first right + 1 elements are the first left elements followed by nums[left..right], so removing the first left elements' sum leaves exactly that range.
17 return self.prefix[right + 1] - self.prefix[left]
Invariant: self.prefix[i] is the sum of the first i elements, nums[0..i-1], for every i from 0 to n, so self.prefix[0] = 0.

Build self.prefix once, with one more slot than nums has elements. self.prefix[0] = 0, and each next slot adds one element: self.prefix[i + 1] = self.prefix[i] + num. Then sumRange answers any range with one subtraction, self.prefix[right + 1] - self.prefix[left]. This is correct because the first right + 1 elements are the first left elements followed by nums[left..right]. Subtracting the sum of the first left elements leaves exactly the range. The leading 0 is the sum of no elements, so left = 0 uses the same formula with no special case. The trap is the index shift. self.prefix[i] stops just before nums[i], so the range ends at self.prefix[right + 1]. Writing self.prefix[right] leaves nums[right] out of every answer.

See it run

Prefix Sums, step by step

answers []

answers []

answers []

answers []

answers []

answers []

answers []

answers []

answers [1]

answers [1]

answers [1,-1]

answers [1,-1,-3]

answers [1,-1,-3] · answer [1,-1,-3]

The top row is nums. The bottom row is prefix. A value of nums sits above the prefix slot it is added into, so nums[i] is above prefix[i + 1].

  • num the value nums[i] that is added to the running total
  • prev the earlier slot prefix[i] that the new slot builds on
  • new the prefix slot that is written in this step
  • in range a value of nums inside the range left..right
  • prefix[left] the total of the values before the range
  • prefix[right + 1] the total of the values up to and including the range
  • too early prefix[right], which stops one value before the range ends

Start: nums has 6 values. prefix has 7 slots, one more than nums, and prefix[i] will be the sum of the first i values.

prefix[0] = 0, the sum of no values. This slot lets a range that starts at index 0 use the same formula.

prefix[1] = prefix[0] + nums[0] = 0 + -2 = -2.

prefix[2] = prefix[1] + nums[1] = -2 + 0 = -2.

prefix[3] = prefix[2] + nums[2] = -2 + 3 = 1.

prefix[4] = prefix[3] + nums[3] = 1 + -5 = -4.

prefix[5] = prefix[4] + nums[4] = -4 + 2 = -2.

prefix[6] = prefix[5] + nums[5] = -2 + -1 = -3.

sumRange(0, 2) = prefix[3] - prefix[0] = 1 - 0 = 1.

Trap: prefix[right] = prefix[2] = -2 stops before nums[2]. Then -2 - 0 = -2 is one value short.

sumRange(2, 5) = prefix[6] - prefix[2] = -3 - -2 = -1.

sumRange(0, 5) = prefix[6] - prefix[0] = -3 - 0 = -3.

Done: the answers are [1, -1, -3]. Each query cost one subtraction.

nums
prefix[0]
prefix[1]-2
prefix[2]0
prefix[3]3
prefix[4]-5
prefix[5]2
prefix[6]-1
prefix
1 / 13
Gotchas1 of 3

Gotcha 1 of 3

  1. 1
    The trap

    Gotcha 1: Writing self.prefix[right] instead of self.prefix[right + 1]

    Mistake
    return self.prefix[right] - self.prefix[left]
    Fix
    return self.prefix[right + 1] - self.prefix[left]

    What happens: self.prefix[i] is the sum of the elements before index i. So self.prefix[right] stops just before nums[right]. The subtraction then returns nums[left..right - 1], one element short. On a one-element range it returns 0.

    Example: nums = [-2, 0, 3, -5, 2, -1], sumRange(0, 2)
    Mistake: returns -2Fix: returns 1
  2. 2

    Gotcha 2: Dropping the leading 0 and reading self.prefix[left - 1]

    Mistake
    self.prefix = []  # prefix[i] = sum of nums[0..i]
    ...
    return self.prefix[right] - self.prefix[left - 1]
    Fix
    self.prefix = [0] * (len(nums) + 1)
    ...
    return self.prefix[right + 1] - self.prefix[left]

    What happens: Without the leading 0, the list has n slots and self.prefix[i] includes nums[i]. A range then needs self.prefix[left - 1]. When left = 0, that is self.prefix[-1]. Python reads the last slot, the sum of the whole array, and raises no error. So a range that starts at index 0 is wrong whenever the whole array does not sum to 0.

    Example: nums = [-2, 0, 3, -5, 2, -1], sumRange(0, 2)
    Mistake: returns 4Fix: returns 1
  3. 3

    Gotcha 3: Adding up the range inside sumRange

    Mistake
    self.nums = nums
    ...
    return sum(self.nums[left : right + 1])
    Fix
    return self.prefix[right + 1] - self.prefix[left]

    What happens: The answers stay correct, but each call reads every element of its range. With 10^4 elements and 10^4 calls, that is up to 10^8 additions. The prefix array does the adding once, in __init__. After that, every call is one subtraction.

Not this one if…
  • The target sum is given, and the ranges are unknown, as in Subarray Sum Equals K (LC 560). You must find or count the ranges that reach the sum. See Prefix Sum Map
  • Each input adds a value to every element of a range, as in Corporate Flight Bookings (LC 1109). The ranges are written to, not read, and you return the final array. See Difference Array
  • The array changes between the range questions, as in Range Sum Query - Mutable (LC 307): one update changes every prefix total after it. See Fenwick Tree

Deep Dive

Why one subtraction gives any range

prefix[i] is the sum of the first i values, nums[0..i-1]. The first right + 1 values are the first left values followed by nums[left..right]. So prefix[right + 1] equals prefix[left] plus the sum of the range.

Move prefix[left] to the other side. The sum of the range is prefix[right + 1] - prefix[left]. This is one subtraction, however long the range is.

The extra slot prefix[0] = 0 is the sum of no values. A range that starts at index 0 has left = 0, and its formula is prefix[right + 1] - prefix[0]. No special case is needed.

The cost is O(N) to build prefix once and O(1) for every sumRange call. Without prefix, each call adds up to the whole range. With 10^4 values and 10^4 calls, that is up to 10^8 additions.

Building prefix and answering three queries

The input is nums = [-2, 0, 3, -5, 2, -1] from Range Sum Query - Immutable (LC 303). The build gives prefix = [0, -2, -2, 1, -4, -2, -3]. Each slot is the slot before it plus one value.

sumRange(0, 2) is prefix[3] - prefix[0] = 1 - 0 = 1. Check: -2 + 0 + 3 = 1.

sumRange(2, 5) is prefix[6] - prefix[2] = -3 - (-2) = -1. Check: 3 - 5 + 2 - 1 = -1.

sumRange(0, 5) is prefix[6] - prefix[0] = -3. These three answers match the examples of the problem.

The trap is the index shift. prefix[right] stops before nums[right]. For sumRange(0, 2) it gives prefix[2] - prefix[0] = -2, which is one value short.

Which operations work, and what the same array gives

The method needs an operation that can be undone. Subtraction undoes addition, so sums work. Counts work for the same reason. XOR is its own undo, so XOR works. In XOR Queries of a Subarray (LC 1310), arr = [1, 3, 4, 8] has the query answers [2, 7, 14, 8] for the ranges [0, 1], [1, 2], [0, 3] and [3, 3].

A maximum or a minimum cannot be undone. Once a larger value is in prefix, taking it out is impossible, so this method does not give the maximum of a range.

The array also answers questions about positions. In Find Pivot Index (LC 724), the sum left of index i is prefix[i], and the sum right of it is prefix[n] - prefix[i + 1]. For [1, 7, 3, 6, 5, 6] the two sums are equal at index 3.

For a grid, as in Range Sum Query 2D - Immutable (LC 304), prefix[r][c] holds the sum of the rectangle of the first r rows and the first c columns. The sum of a rectangle with corners (r1, c1) and (r2, c2) is prefix[r2 + 1][c2 + 1] - prefix[r1][c2 + 1] - prefix[r2 + 1][c1] + prefix[r1][c1]. The last term adds back the corner that both subtractions removed.

60-Second Pitch

"The array does not change and there are many range questions, so I do the adding once. I build prefix with one extra slot, where prefix[0] is 0 and prefix[i + 1] is prefix[i] plus one value. Then prefix[i] is the sum of the first i values. A range from left to right is the first right + 1 values minus the first left values. So the answer is prefix[right + 1] - prefix[left], one subtraction. The build is O(N) time, each query is O(1), and the extra space is O(N). The extra slot removes the special case for left = 0."

Common Questions

Why does prefix have one more slot than nums?

prefix[0] = 0 is the sum of no values. With it, a range that starts at index 0 uses the same formula as every other range. Without it, left = 0 needs a special case or reads prefix[-1].

What is the cost of building and of querying?

The build costs O(N) time and O(N) space. Each query is one subtraction, so it costs O(1). For Q queries the total is O(N + Q), and the direct way costs up to O(N * Q).

Does it work for a maximum or a minimum?

No. The formula needs an operation that can be undone, and a maximum cannot be undone. Sums, counts and XOR can be undone. A range maximum needs a sparse table or a segment tree.

What changes if the array can be updated between queries?

One update would change every slot after it, which costs O(N). A Fenwick Tree keeps the same range sums with O(log N) for an update and for a query.

How do you extend it to a two-dimensional grid?

Store prefix[r][c] as the sum of the first r rows and first c columns. Build it with prefix[r + 1][c + 1] = M[r][c] + prefix[r][c + 1] + prefix[r + 1][c] - prefix[r][c]. A query adds the large rectangle, subtracts the two strips, and adds back the corner they both removed.

SWE Reasoning

Spot it

  • The array does not change, and there are many questions about the sum, count or XOR of a range left..right.
  • The operation can be undone: sum, count or XOR.
  • A question needs the total to the left or to the right of each position (Find Pivot Index (LC 724)).
  • Example problems: Range Sum Query - Immutable (LC 303), Range Sum Query 2D - Immutable (LC 304), XOR Queries of a Subarray (LC 1310).

The invariant, step by step

  1. self.prefix has n + 1 slots, and self.prefix[0] = 0.
  2. The build loop sets self.prefix[i + 1] = self.prefix[i] + num.
  3. So self.prefix[i] is the sum of nums[0..i-1] for every i.
  4. The first right + 1 values are the first left values followed by the range.
  5. So self.prefix[right + 1] - self.prefix[left] is exactly the sum of nums[left..right].

Traps

  • Reading self.prefix[right]. The slot stops before nums[right], and every answer is one value short.
  • Leaving out the leading 0. A range with left = 0 then needs self.prefix[left - 1], which is self.prefix[-1] in Python. It reads the last slot without an error and gives a wrong answer.
  • Adding up the range inside sumRange. The answers are correct, but each call costs the length of the range.
  • Using the method on an array that changes. One update changes every slot after it. The Fenwick Tree card handles that case.

Practice

Canonical LeetCode:Range Sum Query - Immutable (LC 303)Range Sum Query 2D - Immutable (LC 304)Matrix Block Sum (LC 1314)Find the Highest Altitude (LC 1732)Number of Ways to Split Array (LC 2270)Count Vowel Strings in Ranges (LC 2559)XOR Queries of a Subarray (LC 1310)Special Array II (LC 3152)Find Pivot Index (LC 724)Running Sum of 1d Array (LC 1480)Minimum Value to Get Positive Step by Step Sum (LC 1413)
Theory Context•Two Pointers & Sliding Window
P2EasyMental model~3 min speedrun

Prefix Sum Array

Grows into
Prefix-Suffix ScanPrefix Sum MapDifference Array
Unlocks
Fenwick TreeSegment Tree

Range Sum Query - Immutable (LC 303)(opens leetcode.com)

An integer array nums is given once, and it never changes. After that, many questions arrive. Each question names two indices, left and right, with left <= right, and asks for the total of the elements from index left to index right, both ends included. That total is the sum below.

nums[left] + nums[left + 1] + ... + nums[right]

Your class is NumArray. Its constructor, NumArray(nums), receives the array. Its method sumRange(left, right) answers one question and returns that total as an integer.

Example 1
Input:["NumArray", "sumRange", "sumRange", "sumRange"] [[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]]
Output:[null, 1, -1, -3]
NumArray([-2, 0, 3, -5, 2, -1]) receives the array and returns null. sumRange(0, 2) is -2 + 0 + 3 = 1. sumRange(2, 5) is 3 + (-5) + 2 + (-1) = -1. sumRange(0, 5) is the whole array: -2 + 0 + 3 + (-5) + 2 + (-1) = -3.
-200132-5324-15leftright

Prefix Sums

nums ➔ prefix with n + 1 slots, prefix[0] = 0 ➔ prefix[right + 1] - prefix[left]
Spot it when: The array does not change, and you must answer many questions about the sum, count or XOR of a given range left..right. Or you need the running total at each position, counted from the start.
Prefix Sums
Python
1class NumArray:
2 """Range Sum Query - Immutable: build the prefix array once, then answer each range with one subtraction.
3 Time: O(N) to build, O(1) per sumRange | Space: O(N)
4 """
5 
6 def __init__(self, nums: list[int]) -> None:
7 # prefix[i]: sum of the first i elements of nums; prefix[0] = 0, the sum of no elements
8 self.prefix = [0] * (len(nums) + 1)
9 # 1. Build The Prefix Array Once:
10 # Each next slot adds one more element to the previous running total, so prefix[i + 1] is always one element ahead of prefix[i].
11 for i, num in enumerate(nums):
12 self.prefix[i + 1] = self.prefix[i] + num
13 
14 def sumRange(self, left: int, right: int) -> int:
15 # 1. Subtract The Two Running Totals:
16 # The first right + 1 elements are the first left elements followed by nums[left..right], so removing the first left elements' sum leaves exactly that range.
17 return self.prefix[right + 1] - self.prefix[left]
Invariant: self.prefix[i] is the sum of the first i elements, nums[0..i-1], for every i from 0 to n, so self.prefix[0] = 0.

Build self.prefix once, with one more slot than nums has elements. self.prefix[0] = 0, and each next slot adds one element: self.prefix[i + 1] = self.prefix[i] + num. Then sumRange answers any range with one subtraction, self.prefix[right + 1] - self.prefix[left]. This is correct because the first right + 1 elements are the first left elements followed by nums[left..right]. Subtracting the sum of the first left elements leaves exactly the range. The leading 0 is the sum of no elements, so left = 0 uses the same formula with no special case. The trap is the index shift. self.prefix[i] stops just before nums[i], so the range ends at self.prefix[right + 1]. Writing self.prefix[right] leaves nums[right] out of every answer.

See it run

Prefix Sums, step by step

answers []

answers []

answers []

answers []

answers []

answers []

answers []

answers []

answers [1]

answers [1]

answers [1,-1]

answers [1,-1,-3]

answers [1,-1,-3] · answer [1,-1,-3]

The top row is nums. The bottom row is prefix. A value of nums sits above the prefix slot it is added into, so nums[i] is above prefix[i + 1].

  • num the value nums[i] that is added to the running total
  • prev the earlier slot prefix[i] that the new slot builds on
  • new the prefix slot that is written in this step
  • in range a value of nums inside the range left..right
  • prefix[left] the total of the values before the range
  • prefix[right + 1] the total of the values up to and including the range
  • too early prefix[right], which stops one value before the range ends

Start: nums has 6 values. prefix has 7 slots, one more than nums, and prefix[i] will be the sum of the first i values.

prefix[0] = 0, the sum of no values. This slot lets a range that starts at index 0 use the same formula.

prefix[1] = prefix[0] + nums[0] = 0 + -2 = -2.

prefix[2] = prefix[1] + nums[1] = -2 + 0 = -2.

prefix[3] = prefix[2] + nums[2] = -2 + 3 = 1.

prefix[4] = prefix[3] + nums[3] = 1 + -5 = -4.

prefix[5] = prefix[4] + nums[4] = -4 + 2 = -2.

prefix[6] = prefix[5] + nums[5] = -2 + -1 = -3.

sumRange(0, 2) = prefix[3] - prefix[0] = 1 - 0 = 1.

Trap: prefix[right] = prefix[2] = -2 stops before nums[2]. Then -2 - 0 = -2 is one value short.

sumRange(2, 5) = prefix[6] - prefix[2] = -3 - -2 = -1.

sumRange(0, 5) = prefix[6] - prefix[0] = -3 - 0 = -3.

Done: the answers are [1, -1, -3]. Each query cost one subtraction.

nums
prefix[0]
prefix[1]-2
prefix[2]0
prefix[3]3
prefix[4]-5
prefix[5]2
prefix[6]-1
prefix
1 / 13
Gotchas1 of 3

Gotcha 1 of 3

  1. 1
    The trap

    Gotcha 1: Writing self.prefix[right] instead of self.prefix[right + 1]

    Mistake
    return self.prefix[right] - self.prefix[left]
    Fix
    return self.prefix[right + 1] - self.prefix[left]

    What happens: self.prefix[i] is the sum of the elements before index i. So self.prefix[right] stops just before nums[right]. The subtraction then returns nums[left..right - 1], one element short. On a one-element range it returns 0.

    Example: nums = [-2, 0, 3, -5, 2, -1], sumRange(0, 2)
    Mistake: returns -2Fix: returns 1
  2. 2

    Gotcha 2: Dropping the leading 0 and reading self.prefix[left - 1]

    Mistake
    self.prefix = []  # prefix[i] = sum of nums[0..i]
    ...
    return self.prefix[right] - self.prefix[left - 1]
    Fix
    self.prefix = [0] * (len(nums) + 1)
    ...
    return self.prefix[right + 1] - self.prefix[left]

    What happens: Without the leading 0, the list has n slots and self.prefix[i] includes nums[i]. A range then needs self.prefix[left - 1]. When left = 0, that is self.prefix[-1]. Python reads the last slot, the sum of the whole array, and raises no error. So a range that starts at index 0 is wrong whenever the whole array does not sum to 0.

    Example: nums = [-2, 0, 3, -5, 2, -1], sumRange(0, 2)
    Mistake: returns 4Fix: returns 1
  3. 3

    Gotcha 3: Adding up the range inside sumRange

    Mistake
    self.nums = nums
    ...
    return sum(self.nums[left : right + 1])
    Fix
    return self.prefix[right + 1] - self.prefix[left]

    What happens: The answers stay correct, but each call reads every element of its range. With 10^4 elements and 10^4 calls, that is up to 10^8 additions. The prefix array does the adding once, in __init__. After that, every call is one subtraction.

Not this one if…
  • The target sum is given, and the ranges are unknown, as in Subarray Sum Equals K (LC 560). You must find or count the ranges that reach the sum. See Prefix Sum Map
  • Each input adds a value to every element of a range, as in Corporate Flight Bookings (LC 1109). The ranges are written to, not read, and you return the final array. See Difference Array
  • The array changes between the range questions, as in Range Sum Query - Mutable (LC 307): one update changes every prefix total after it. See Fenwick Tree

Deep Dive

Why one subtraction gives any range

prefix[i] is the sum of the first i values, nums[0..i-1]. The first right + 1 values are the first left values followed by nums[left..right]. So prefix[right + 1] equals prefix[left] plus the sum of the range.

Move prefix[left] to the other side. The sum of the range is prefix[right + 1] - prefix[left]. This is one subtraction, however long the range is.

The extra slot prefix[0] = 0 is the sum of no values. A range that starts at index 0 has left = 0, and its formula is prefix[right + 1] - prefix[0]. No special case is needed.

The cost is O(N) to build prefix once and O(1) for every sumRange call. Without prefix, each call adds up to the whole range. With 10^4 values and 10^4 calls, that is up to 10^8 additions.

Building prefix and answering three queries

The input is nums = [-2, 0, 3, -5, 2, -1] from Range Sum Query - Immutable (LC 303). The build gives prefix = [0, -2, -2, 1, -4, -2, -3]. Each slot is the slot before it plus one value.

sumRange(0, 2) is prefix[3] - prefix[0] = 1 - 0 = 1. Check: -2 + 0 + 3 = 1.

sumRange(2, 5) is prefix[6] - prefix[2] = -3 - (-2) = -1. Check: 3 - 5 + 2 - 1 = -1.

sumRange(0, 5) is prefix[6] - prefix[0] = -3. These three answers match the examples of the problem.

The trap is the index shift. prefix[right] stops before nums[right]. For sumRange(0, 2) it gives prefix[2] - prefix[0] = -2, which is one value short.

Which operations work, and what the same array gives

The method needs an operation that can be undone. Subtraction undoes addition, so sums work. Counts work for the same reason. XOR is its own undo, so XOR works. In XOR Queries of a Subarray (LC 1310), arr = [1, 3, 4, 8] has the query answers [2, 7, 14, 8] for the ranges [0, 1], [1, 2], [0, 3] and [3, 3].

A maximum or a minimum cannot be undone. Once a larger value is in prefix, taking it out is impossible, so this method does not give the maximum of a range.

The array also answers questions about positions. In Find Pivot Index (LC 724), the sum left of index i is prefix[i], and the sum right of it is prefix[n] - prefix[i + 1]. For [1, 7, 3, 6, 5, 6] the two sums are equal at index 3.

For a grid, as in Range Sum Query 2D - Immutable (LC 304), prefix[r][c] holds the sum of the rectangle of the first r rows and the first c columns. The sum of a rectangle with corners (r1, c1) and (r2, c2) is prefix[r2 + 1][c2 + 1] - prefix[r1][c2 + 1] - prefix[r2 + 1][c1] + prefix[r1][c1]. The last term adds back the corner that both subtractions removed.

60-Second Pitch

"The array does not change and there are many range questions, so I do the adding once. I build prefix with one extra slot, where prefix[0] is 0 and prefix[i + 1] is prefix[i] plus one value. Then prefix[i] is the sum of the first i values. A range from left to right is the first right + 1 values minus the first left values. So the answer is prefix[right + 1] - prefix[left], one subtraction. The build is O(N) time, each query is O(1), and the extra space is O(N). The extra slot removes the special case for left = 0."

Common Questions

Why does prefix have one more slot than nums?

prefix[0] = 0 is the sum of no values. With it, a range that starts at index 0 uses the same formula as every other range. Without it, left = 0 needs a special case or reads prefix[-1].

What is the cost of building and of querying?

The build costs O(N) time and O(N) space. Each query is one subtraction, so it costs O(1). For Q queries the total is O(N + Q), and the direct way costs up to O(N * Q).

Does it work for a maximum or a minimum?

No. The formula needs an operation that can be undone, and a maximum cannot be undone. Sums, counts and XOR can be undone. A range maximum needs a sparse table or a segment tree.

What changes if the array can be updated between queries?

One update would change every slot after it, which costs O(N). A Fenwick Tree keeps the same range sums with O(log N) for an update and for a query.

How do you extend it to a two-dimensional grid?

Store prefix[r][c] as the sum of the first r rows and first c columns. Build it with prefix[r + 1][c + 1] = M[r][c] + prefix[r][c + 1] + prefix[r + 1][c] - prefix[r][c]. A query adds the large rectangle, subtracts the two strips, and adds back the corner they both removed.

SWE Reasoning

Spot it

  • The array does not change, and there are many questions about the sum, count or XOR of a range left..right.
  • The operation can be undone: sum, count or XOR.
  • A question needs the total to the left or to the right of each position (Find Pivot Index (LC 724)).
  • Example problems: Range Sum Query - Immutable (LC 303), Range Sum Query 2D - Immutable (LC 304), XOR Queries of a Subarray (LC 1310).

The invariant, step by step

  1. self.prefix has n + 1 slots, and self.prefix[0] = 0.
  2. The build loop sets self.prefix[i + 1] = self.prefix[i] + num.
  3. So self.prefix[i] is the sum of nums[0..i-1] for every i.
  4. The first right + 1 values are the first left values followed by the range.
  5. So self.prefix[right + 1] - self.prefix[left] is exactly the sum of nums[left..right].

Traps

  • Reading self.prefix[right]. The slot stops before nums[right], and every answer is one value short.
  • Leaving out the leading 0. A range with left = 0 then needs self.prefix[left - 1], which is self.prefix[-1] in Python. It reads the last slot without an error and gives a wrong answer.
  • Adding up the range inside sumRange. The answers are correct, but each call costs the length of the range.
  • Using the method on an array that changes. One update changes every slot after it. The Fenwick Tree card handles that case.

Practice

Canonical LeetCode:Range Sum Query - Immutable (LC 303)Range Sum Query 2D - Immutable (LC 304)Matrix Block Sum (LC 1314)Find the Highest Altitude (LC 1732)Number of Ways to Split Array (LC 2270)Count Vowel Strings in Ranges (LC 2559)XOR Queries of a Subarray (LC 1310)Special Array II (LC 3152)Find Pivot Index (LC 724)Running Sum of 1d Array (LC 1480)Minimum Value to Get Positive Step by Step Sum (LC 1413)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•