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•Advanced Data Structures

Builds on Prefix Sum Map, Kernighan's Bit Trick and Prefix Sums, which you haven’t finished yet.

P3HardMental model~6 min speedrun

Fenwick Tree (Binary Indexed Tree)

Range Sum Query - Mutable (LC 307)(opens leetcode.com)

You get an integer array nums, followed by a mix of two kinds of requests in any order: change one element, or report the sum of a contiguous stretch of elements.

Build the NumArray class:

  • NumArray(nums) stores the array.
  • update(index, val) sets nums[index] to val.
  • sumRange(left, right) returns nums[left] + nums[left + 1] + ... + nums[right], both ends included (left <= right).
Example 1
Input:["NumArray", "sumRange", "update", "sumRange"] [[[1, 3, 5]], [0, 2], [1, 2], [0, 2]]
Output:[null, 9, null, 8]
The array starts as [1, 3, 5], so sumRange(0, 2) is 1 + 3 + 5 = 9. update(1, 2) makes it [1, 2, 5], and the next sumRange(0, 2) is 1 + 2 + 5 = 8.
103152update(1, 2)

Fenwick Tree

array ➔ isolate lowbit = x & (-x) ➔ O(log N) prefix query & update
Spot it when: The array changes (single-element updates) and you still need prefix or range sums fast, or you count how many earlier values are smaller as you go.
Fenwick Tree
Python
1class FenwickTree:
2 """Binary Indexed Tree (Fenwick Tree) for 1-indexed range sums.
3 Time: O(log N) update & query | Space: O(N)
4 """
5 def __init__(self, n: int):
6 self.n = n
7 self.tree = [0] * (n + 1)
8 
9 def update(self, i: int, delta: int) -> None:
10 """Add delta to index i (1-indexed)."""
11 # 1. Walk Up, Fixing Every Block That Covers i:
12 # Adding the lowest set bit jumps from i to the next larger block that still contains it,
13 # so this visits exactly the blocks whose stored sum needs delta added.
14 while i <= self.n:
15 self.tree[i] += delta
16 i += i & (-i) # Step up to parent covering range
17 
18 def query(self, i: int) -> int:
19 """Sum elements from 1 to i (1-indexed)."""
20 total = 0
21 
22 # 1. Walk Down, Adding Disjoint Blocks:
23 # Removing the lowest set bit jumps to just before the block tree[i] covers, so each block
24 # added is disjoint from the ones before it, and together they cover exactly 1..i once.
25 while i > 0:
26 total += self.tree[i]
27 i -= i & (-i) # Step down to next disjoint prefix
28 return total
29 
30 def range_query(self, left: int, right: int) -> int:
31 """Sum in range [left, right] inclusive."""
32 return self.query(right) - self.query(left - 1)
33 
34 
35class NumArray:
36 """Range Sum Query - Mutable: keep the values, send each change to the Fenwick tree."""
37 def __init__(self, nums: list[int]):
38 self.nums = list(nums)
39 self.fenwick = FenwickTree(len(nums))
40 
41 # 1. Seed The Tree With Every Starting Value:
42 # Each 0-based position lives one slot higher in the 1-indexed tree, so index + 1 is its i.
43 for index, val in enumerate(nums):
44 self.fenwick.update(index + 1, val) # Position index lives at i = index + 1
45 
46 def update(self, index: int, val: int) -> None:
47 self.fenwick.update(index + 1, val - self.nums[index]) # Add the change, not the new value
48 self.nums[index] = val
49 
50 def sumRange(self, left: int, right: int) -> int:
51 return self.fenwick.range_query(left + 1, right + 1)
Invariant: self.tree[i] always equals the sum of the values at positions i - (i & -i) + 1 through i.

Let tree[i] hold the sum of the block of i & (-i) values that ends at position i: query adds whole blocks while i -= i & (-i) walks down, and update fixes every block that contains i while i += i & (-i) walks up. Removing the lowest set bit jumps to just before the block tree[i] covers, so the blocks query visits never overlap and together cover exactly 1..i; adding it lands on the next larger block that also contains i. The trap is index 0: the tree is 1-indexed, and i & (-i) is 0 when i is 0, so update(0, ...) never moves and loops forever; shift every position up by one.

See it run

A Fenwick tree: query, update, query again

i - · total - · delta -

i 7 · total -3 · delta -

i 6 · total 6 · delta -

i 4 · total 16 · delta -

i 0 · total 16 · delta -

i 3 · total - · delta 5

i 3 · total - · delta 5

i 4 · total - · delta 5

i 8 · total - · delta 5

i 16 · total - · delta 5

i 7 · total -3 · delta -

i 6 · total 6 · delta -

i 4 · total 21 · delta -

i 0 · total 21 · delta - · answer 21

The column label is the position i. The first row holds the values, and the second row holds tree[i].

  • tree[i] the tree entry read or written at this step
  • block the values that tree[i] sums
  • changed the value that update changes

The tree is built by one update per value. Each tree[i] holds the sum of the block of i & -i values that ends at position i.

i = 7: add tree[7] = -3, the sum of positions 7 to 7. Then i becomes 7 - 1 = 6.

i = 6: add tree[6] = 9, the sum of positions 5 to 6. Then i becomes 6 - 2 = 4.

i = 4: add tree[4] = 10, the sum of positions 1 to 4. Then i becomes 4 - 4 = 0.

query(7) = 16. The blocks added cover positions 1 to 7 once, so sumRange(0, 6) = 16.

nums[2] changes from -1 to 4, so delta = 5. Start at i = index + 1 = 3: from i = 0 the loop would never move.

i = 3: tree[3] covers position 3, so it changes from -1 to 4. Then i becomes 3 + 1 = 4.

i = 4: tree[4] covers position 3, so it changes from 10 to 15. Then i becomes 4 + 4 = 8.

i = 8: tree[8] covers position 3, so it changes from 19 to 24. Then i becomes 8 + 8 = 16.

i = 16 is larger than n = 8, so the update stops. Every block that contains position 3 now includes delta.

i = 7: add tree[7] = -3, the sum of positions 7 to 7. Then i becomes 7 - 1 = 6.

i = 6: add tree[6] = 9, the sum of positions 5 to 6. Then i becomes 6 - 2 = 4.

i = 4: add tree[4] = 15, the sum of positions 1 to 4. Then i becomes 4 - 4 = 0.

Done: query(7) = 21 adds blocks that cover positions 1 to 7 once, so sumRange(0, 6) = 21. Before the update it was 16.

nums
13
22
3-1
46
55
64
7-3
83
tree
3
5
-1
10
5
9
-3
19
1 / 14
Gotchas1 of 5

Gotcha 1 of 5

  1. 1
    The trap

    Gotcha 1: Calling update(index, val) with a 0-based index

    Mistake
    self.fenwick.update(index, val)
    Fix
    self.fenwick.update(index + 1, val)

    What happens: The tree is 1-indexed. For i = 0, i & (-i) is 0, so i += i & (-i) leaves i at 0. The loop while i <= self.n then never ends. Shift every position up by one, so nums[index] lives at i = index + 1.

    Example: nums = [1, 3, 5], then sumRange(0, 2)
    Mistake: the loop never endsFix: returns 9
  2. 2

    Gotcha 2: Shifting right but not left in sumRange

    Mistake
    return self.fenwick.range_query(left, right + 1)
    Fix
    return self.fenwick.range_query(left + 1, right + 1)

    What happens: range_query(left, right) returns query(right) - query(left - 1). With left not shifted, it subtracts the sum up to position left - 1. Position left - 1 holds nums[left - 2], so the result also counts nums[left - 1].

    Example: nums = [1, 3, 5], then sumRange(1, 2)
    Mistake: returns 9Fix: returns 8
  3. 3

    Gotcha 3: Adding the new value instead of the change

    Mistake
    self.fenwick.update(index + 1, val)
    Fix
    self.fenwick.update(index + 1, val - self.nums[index])

    What happens: FenwickTree.update adds delta to a position. It can't set a value. To change nums[index] to val, add val - self.nums[index]. The blueprint keeps the current values in self.nums for this subtraction.

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

    Gotcha 4: Using raw values as positions instead of ranks 1..m

    What happens: In Count of Smaller Numbers After Self (LC 315), the values can be negative or 0. From a position of 0 or below, i += i & (-i) reaches 0 and stays there. So update never ends. Replace each value by its rank 1..m among the sorted distinct values first. Then scan nums from the right.

  5. 5

    Gotcha 5: Building with n calls to update when O(n) is needed

    Mistake
    for index, val in enumerate(nums):
        self.fenwick.update(index + 1, val)
    Fix
    for i in range(1, n + 1):
        self.tree[i] += nums[i - 1]
        parent = i + (i & -i)
        if parent <= n:
            self.tree[parent] += self.tree[i]

    What happens: Each update visits up to log n positions, so n calls cost O(n log n) time. The one-pass build adds each tree[i] into its parent i + (i & -i) once. It gives the same tree in O(n) time. The sums are the same, and only the time changes.

Not this one if…
  • Queries ask for the maximum over a range of values that keep being overwritten, as in Longest Increasing Subsequence II (LC 2407): a maximum can't be split into a difference of two prefixes. See Max Segment Tree
  • All the updates come first and every value is read once at the end, as in Corporate Flight Bookings (LC 1109): updates and queries never mix. See Difference Array

Deep Dive

What tree[i] stores

tree[i] is the sum of a block of values that ends at position i. The block has i & -i values. The expression i & -i keeps only the lowest set bit of i, so it is the largest power of 2 that divides i.

Take nums = [3, 2, -1, 6, 5, 4, -3, 3]. The positions are 1 to 8. Position 6 is binary 110, its lowest set bit is 2, so tree[6] sums positions 5 and 6: 5 + 4 = 9. Position 4 is binary 100, so tree[4] sums positions 1 to 4: 3 + 2 - 1 + 6 = 10. Position 7 is odd, so tree[7] is just -3.

The full tree is [3, 5, -1, 10, 5, 9, -3, 19]. This matches the card's invariant: tree[i] is the sum of positions i - (i & -i) + 1 through i.

query walks down and update walks up

query(7) starts at i = 7. It adds tree[7], then removes the lowest set bit: 7 becomes 6, then 4, then 0. The blocks are position 7, positions 5 to 6, and positions 1 to 4. They do not overlap, and they cover positions 1 to 7 once. The total is -3 + 9 + 10 = 16.

Each step removes one set bit, so the loop runs at most as many times as i has set bits. That is at most floor(log2 n) + 1 steps.

update goes the other way. Changing position 3 must change every tree[k] whose block contains position 3. Those are tree[3], tree[4] and tree[8]. Adding the lowest set bit moves from 3 to 4 to 8, and the next step gives 16, which is larger than n, so the loop stops.

Example: nums[2] changes from -1 to 4. The change is 5. The three entries become 4, 15 and 24. Then query(7) visits the same blocks as before and returns -3 + 9 + 15 = 21. Only tree[4] changed among them, and it changed by exactly 5.

Add the change, and shift the index by one

FenwickTree.update adds a change to a block. It cannot set a value. So NumArray.update adds val - self.nums[index], the difference between the new and the old value. The blueprint keeps self.nums for this subtraction.

The tree is 1-indexed because the block of i is defined with i & -i. For i = 0, the expression 0 & -0 is 0, so i += i & -i leaves i at 0. The loop while i <= self.n then never ends. So nums[index] lives at i = index + 1.

sumRange(left, right) calls range_query(left + 1, right + 1). This returns query(right + 1) - query(left). The subtraction removes the prefix before left. For nums = [1, 3, 5] in Range Sum Query - Mutable (LC 307), sumRange(0, 2) is 9. After update(1, 2), it is 8.

Cost, building, and ranks

update and query each take O(log N) time. The tree takes O(N) space. Building it with N calls to update takes O(N log N) time. A single pass is faster: for each i, add tree[i] into tree[i + (i & -i)] when that position is at most n. This gives the same tree in O(N) time.

A Fenwick tree needs a way to remove the prefix before left, so it works for sums, counts and XOR, where subtraction undoes addition. It does not work as written for the minimum or the maximum, because there is no subtraction for them. A Segment Tree handles those.

To count values, use the tree as a table of counts. In Count of Smaller Numbers After Self (LC 315), replace each value by its rank from 1 to m among the sorted distinct values. Scan nums from the right. For each value with rank r, query(r - 1) is the number of smaller values already seen, and update(r, 1) records this value. For [5, 2, 6, 1] the answers are [2, 1, 1, 0].

The ranks matter because values can be negative or 0. A position of 0 or below would make update loop forever, as in the previous section.

60-Second Pitch

"A prefix-sum array answers a range sum in O(1) time, but one update rewrites up to N entries. A plain scan makes every query O(N). With many updates and many queries, I use a Fenwick tree. tree[i] holds the sum of the i & -i values that end at position i. query adds a block and removes the lowest set bit, so the blocks do not overlap and cover the prefix once. update adds the lowest set bit to reach every block that contains the position. Each loop runs at most about log N times. So update and sumRange are O(log N), and the space is O(N). I keep the 1-based index, and I add the change val - nums[index], not the new value. If the question asks for counts of earlier smaller values, I rank the values first and use the same tree as a table of counts."

Common Questions

Why use a Fenwick tree and not a prefix-sum array?

A prefix-sum array answers sumRange in O(1) time, but one update changes up to N prefix sums, which costs O(N) time. A Fenwick tree makes both operations O(log N). Use the prefix array when the values never change.

Why does the tree start at index 1?

The block of i has i & -i values. For i = 0, that expression is 0, so the block is empty and the loops i += i & -i and i -= i & -i never move. Starting at 1 keeps every block non-empty.

When would you use a Segment Tree instead?

When the query cannot be answered by subtracting two prefixes, for example the minimum or the maximum of a range. A Fenwick tree is shorter to write and uses less memory for sums, counts and XOR. A Segment Tree also supports range updates with lazy values.

How do you build the tree in O(N) time?

Copy each value into tree[i]. Then for each i from 1 to n, add tree[i] into tree[i + (i & -i)] when that position is at most n. Each entry is added into its parent once, so the build makes N additions.

How does a Fenwick tree solve Count of Smaller Numbers After Self (LC 315)?

Replace each value by its rank from 1 to m. Scan from the right. For the value with rank r, the answer is query(r - 1), and then update(r, 1) marks it as seen. Each step is O(log m), so the total is O(N log N).

SWE Reasoning

Spot it

  • The array changes by single-value updates, and queries for prefix or range sums arrive between the updates.
  • A prefix-sum array answers each query in O(1) time, but one update costs O(N) time. A plain scan costs O(N) per query. Many updates and many queries together are too slow.
  • The question counts earlier values that are smaller (or larger) than the current one, which is a prefix count over value ranks.
  • The operation can be undone by subtraction: sum, count or XOR. If it is a minimum or a maximum, use a Segment Tree instead.

The invariant, step by step

  1. tree[i] stores the sum of the block of i & -i values that ends at position i.
  2. query(i) adds tree[i], then sets i -= i & -i. The next block ends just before the current block starts.
  3. So the blocks that query adds do not overlap, and together they cover positions 1 to i once. The loop ends at i = 0.
  4. A change at position p must reach every block that contains p. update starts at p and sets i += i & -i. Each step moves to the next larger block that still contains p.
  5. The loop stops when i is larger than n. Every tree[i] whose block contains p has received the change, and no other entry has.
  6. So the invariant holds after every update, and every query returns the exact prefix sum.

Traps

  • 0-based index. update(index, val) with index = 0 leaves i at 0 and never ends. Pass index + 1.
  • Shifting only right. range_query(left, right + 1) subtracts the prefix up to left - 1, so the result also includes nums[left - 1]. For nums = [1, 3, 5], sumRange(1, 2) returns 9 and not 8.
  • Adding the new value. update adds a change. Passing val instead of val - self.nums[index] adds the whole new value on top of the old one. [1, 3, 5] with update(1, 2) gives 11 and not 8.
  • Raw values as positions. Negative values or 0 break the 1-indexed walk. Replace each value by its rank first.
  • N updates to build. N calls to update cost O(N log N). Use the one-pass build when O(N) is needed.

Practice

Canonical LeetCode:Range Sum Query - Mutable (LC 307)Count of Smaller Numbers After Self (LC 315)Range Sum Query 2D - Mutable (LC 308)Queries on a Permutation With Key (LC 1409)Reverse Pairs (LC 493)Create Sorted Array through Instructions (LC 1649)
Theory Context•Advanced Data Structures

Builds on Prefix Sum Map, Kernighan's Bit Trick and Prefix Sums, which you haven’t finished yet.

P3HardMental model~6 min speedrun

Fenwick Tree (Binary Indexed Tree)

Range Sum Query - Mutable (LC 307)(opens leetcode.com)

You get an integer array nums, followed by a mix of two kinds of requests in any order: change one element, or report the sum of a contiguous stretch of elements.

Build the NumArray class:

  • NumArray(nums) stores the array.
  • update(index, val) sets nums[index] to val.
  • sumRange(left, right) returns nums[left] + nums[left + 1] + ... + nums[right], both ends included (left <= right).
Example 1
Input:["NumArray", "sumRange", "update", "sumRange"] [[[1, 3, 5]], [0, 2], [1, 2], [0, 2]]
Output:[null, 9, null, 8]
The array starts as [1, 3, 5], so sumRange(0, 2) is 1 + 3 + 5 = 9. update(1, 2) makes it [1, 2, 5], and the next sumRange(0, 2) is 1 + 2 + 5 = 8.
103152update(1, 2)

Fenwick Tree

array ➔ isolate lowbit = x & (-x) ➔ O(log N) prefix query & update
Spot it when: The array changes (single-element updates) and you still need prefix or range sums fast, or you count how many earlier values are smaller as you go.
Fenwick Tree
Python
1class FenwickTree:
2 """Binary Indexed Tree (Fenwick Tree) for 1-indexed range sums.
3 Time: O(log N) update & query | Space: O(N)
4 """
5 def __init__(self, n: int):
6 self.n = n
7 self.tree = [0] * (n + 1)
8 
9 def update(self, i: int, delta: int) -> None:
10 """Add delta to index i (1-indexed)."""
11 # 1. Walk Up, Fixing Every Block That Covers i:
12 # Adding the lowest set bit jumps from i to the next larger block that still contains it,
13 # so this visits exactly the blocks whose stored sum needs delta added.
14 while i <= self.n:
15 self.tree[i] += delta
16 i += i & (-i) # Step up to parent covering range
17 
18 def query(self, i: int) -> int:
19 """Sum elements from 1 to i (1-indexed)."""
20 total = 0
21 
22 # 1. Walk Down, Adding Disjoint Blocks:
23 # Removing the lowest set bit jumps to just before the block tree[i] covers, so each block
24 # added is disjoint from the ones before it, and together they cover exactly 1..i once.
25 while i > 0:
26 total += self.tree[i]
27 i -= i & (-i) # Step down to next disjoint prefix
28 return total
29 
30 def range_query(self, left: int, right: int) -> int:
31 """Sum in range [left, right] inclusive."""
32 return self.query(right) - self.query(left - 1)
33 
34 
35class NumArray:
36 """Range Sum Query - Mutable: keep the values, send each change to the Fenwick tree."""
37 def __init__(self, nums: list[int]):
38 self.nums = list(nums)
39 self.fenwick = FenwickTree(len(nums))
40 
41 # 1. Seed The Tree With Every Starting Value:
42 # Each 0-based position lives one slot higher in the 1-indexed tree, so index + 1 is its i.
43 for index, val in enumerate(nums):
44 self.fenwick.update(index + 1, val) # Position index lives at i = index + 1
45 
46 def update(self, index: int, val: int) -> None:
47 self.fenwick.update(index + 1, val - self.nums[index]) # Add the change, not the new value
48 self.nums[index] = val
49 
50 def sumRange(self, left: int, right: int) -> int:
51 return self.fenwick.range_query(left + 1, right + 1)
Invariant: self.tree[i] always equals the sum of the values at positions i - (i & -i) + 1 through i.

Let tree[i] hold the sum of the block of i & (-i) values that ends at position i: query adds whole blocks while i -= i & (-i) walks down, and update fixes every block that contains i while i += i & (-i) walks up. Removing the lowest set bit jumps to just before the block tree[i] covers, so the blocks query visits never overlap and together cover exactly 1..i; adding it lands on the next larger block that also contains i. The trap is index 0: the tree is 1-indexed, and i & (-i) is 0 when i is 0, so update(0, ...) never moves and loops forever; shift every position up by one.

See it run

A Fenwick tree: query, update, query again

i - · total - · delta -

i 7 · total -3 · delta -

i 6 · total 6 · delta -

i 4 · total 16 · delta -

i 0 · total 16 · delta -

i 3 · total - · delta 5

i 3 · total - · delta 5

i 4 · total - · delta 5

i 8 · total - · delta 5

i 16 · total - · delta 5

i 7 · total -3 · delta -

i 6 · total 6 · delta -

i 4 · total 21 · delta -

i 0 · total 21 · delta - · answer 21

The column label is the position i. The first row holds the values, and the second row holds tree[i].

  • tree[i] the tree entry read or written at this step
  • block the values that tree[i] sums
  • changed the value that update changes

The tree is built by one update per value. Each tree[i] holds the sum of the block of i & -i values that ends at position i.

i = 7: add tree[7] = -3, the sum of positions 7 to 7. Then i becomes 7 - 1 = 6.

i = 6: add tree[6] = 9, the sum of positions 5 to 6. Then i becomes 6 - 2 = 4.

i = 4: add tree[4] = 10, the sum of positions 1 to 4. Then i becomes 4 - 4 = 0.

query(7) = 16. The blocks added cover positions 1 to 7 once, so sumRange(0, 6) = 16.

nums[2] changes from -1 to 4, so delta = 5. Start at i = index + 1 = 3: from i = 0 the loop would never move.

i = 3: tree[3] covers position 3, so it changes from -1 to 4. Then i becomes 3 + 1 = 4.

i = 4: tree[4] covers position 3, so it changes from 10 to 15. Then i becomes 4 + 4 = 8.

i = 8: tree[8] covers position 3, so it changes from 19 to 24. Then i becomes 8 + 8 = 16.

i = 16 is larger than n = 8, so the update stops. Every block that contains position 3 now includes delta.

i = 7: add tree[7] = -3, the sum of positions 7 to 7. Then i becomes 7 - 1 = 6.

i = 6: add tree[6] = 9, the sum of positions 5 to 6. Then i becomes 6 - 2 = 4.

i = 4: add tree[4] = 15, the sum of positions 1 to 4. Then i becomes 4 - 4 = 0.

Done: query(7) = 21 adds blocks that cover positions 1 to 7 once, so sumRange(0, 6) = 21. Before the update it was 16.

nums
13
22
3-1
46
55
64
7-3
83
tree
3
5
-1
10
5
9
-3
19
1 / 14
Gotchas1 of 5

Gotcha 1 of 5

  1. 1
    The trap

    Gotcha 1: Calling update(index, val) with a 0-based index

    Mistake
    self.fenwick.update(index, val)
    Fix
    self.fenwick.update(index + 1, val)

    What happens: The tree is 1-indexed. For i = 0, i & (-i) is 0, so i += i & (-i) leaves i at 0. The loop while i <= self.n then never ends. Shift every position up by one, so nums[index] lives at i = index + 1.

    Example: nums = [1, 3, 5], then sumRange(0, 2)
    Mistake: the loop never endsFix: returns 9
  2. 2

    Gotcha 2: Shifting right but not left in sumRange

    Mistake
    return self.fenwick.range_query(left, right + 1)
    Fix
    return self.fenwick.range_query(left + 1, right + 1)

    What happens: range_query(left, right) returns query(right) - query(left - 1). With left not shifted, it subtracts the sum up to position left - 1. Position left - 1 holds nums[left - 2], so the result also counts nums[left - 1].

    Example: nums = [1, 3, 5], then sumRange(1, 2)
    Mistake: returns 9Fix: returns 8
  3. 3

    Gotcha 3: Adding the new value instead of the change

    Mistake
    self.fenwick.update(index + 1, val)
    Fix
    self.fenwick.update(index + 1, val - self.nums[index])

    What happens: FenwickTree.update adds delta to a position. It can't set a value. To change nums[index] to val, add val - self.nums[index]. The blueprint keeps the current values in self.nums for this subtraction.

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

    Gotcha 4: Using raw values as positions instead of ranks 1..m

    What happens: In Count of Smaller Numbers After Self (LC 315), the values can be negative or 0. From a position of 0 or below, i += i & (-i) reaches 0 and stays there. So update never ends. Replace each value by its rank 1..m among the sorted distinct values first. Then scan nums from the right.

  5. 5

    Gotcha 5: Building with n calls to update when O(n) is needed

    Mistake
    for index, val in enumerate(nums):
        self.fenwick.update(index + 1, val)
    Fix
    for i in range(1, n + 1):
        self.tree[i] += nums[i - 1]
        parent = i + (i & -i)
        if parent <= n:
            self.tree[parent] += self.tree[i]

    What happens: Each update visits up to log n positions, so n calls cost O(n log n) time. The one-pass build adds each tree[i] into its parent i + (i & -i) once. It gives the same tree in O(n) time. The sums are the same, and only the time changes.

Not this one if…
  • Queries ask for the maximum over a range of values that keep being overwritten, as in Longest Increasing Subsequence II (LC 2407): a maximum can't be split into a difference of two prefixes. See Max Segment Tree
  • All the updates come first and every value is read once at the end, as in Corporate Flight Bookings (LC 1109): updates and queries never mix. See Difference Array

Deep Dive

What tree[i] stores

tree[i] is the sum of a block of values that ends at position i. The block has i & -i values. The expression i & -i keeps only the lowest set bit of i, so it is the largest power of 2 that divides i.

Take nums = [3, 2, -1, 6, 5, 4, -3, 3]. The positions are 1 to 8. Position 6 is binary 110, its lowest set bit is 2, so tree[6] sums positions 5 and 6: 5 + 4 = 9. Position 4 is binary 100, so tree[4] sums positions 1 to 4: 3 + 2 - 1 + 6 = 10. Position 7 is odd, so tree[7] is just -3.

The full tree is [3, 5, -1, 10, 5, 9, -3, 19]. This matches the card's invariant: tree[i] is the sum of positions i - (i & -i) + 1 through i.

query walks down and update walks up

query(7) starts at i = 7. It adds tree[7], then removes the lowest set bit: 7 becomes 6, then 4, then 0. The blocks are position 7, positions 5 to 6, and positions 1 to 4. They do not overlap, and they cover positions 1 to 7 once. The total is -3 + 9 + 10 = 16.

Each step removes one set bit, so the loop runs at most as many times as i has set bits. That is at most floor(log2 n) + 1 steps.

update goes the other way. Changing position 3 must change every tree[k] whose block contains position 3. Those are tree[3], tree[4] and tree[8]. Adding the lowest set bit moves from 3 to 4 to 8, and the next step gives 16, which is larger than n, so the loop stops.

Example: nums[2] changes from -1 to 4. The change is 5. The three entries become 4, 15 and 24. Then query(7) visits the same blocks as before and returns -3 + 9 + 15 = 21. Only tree[4] changed among them, and it changed by exactly 5.

Add the change, and shift the index by one

FenwickTree.update adds a change to a block. It cannot set a value. So NumArray.update adds val - self.nums[index], the difference between the new and the old value. The blueprint keeps self.nums for this subtraction.

The tree is 1-indexed because the block of i is defined with i & -i. For i = 0, the expression 0 & -0 is 0, so i += i & -i leaves i at 0. The loop while i <= self.n then never ends. So nums[index] lives at i = index + 1.

sumRange(left, right) calls range_query(left + 1, right + 1). This returns query(right + 1) - query(left). The subtraction removes the prefix before left. For nums = [1, 3, 5] in Range Sum Query - Mutable (LC 307), sumRange(0, 2) is 9. After update(1, 2), it is 8.

Cost, building, and ranks

update and query each take O(log N) time. The tree takes O(N) space. Building it with N calls to update takes O(N log N) time. A single pass is faster: for each i, add tree[i] into tree[i + (i & -i)] when that position is at most n. This gives the same tree in O(N) time.

A Fenwick tree needs a way to remove the prefix before left, so it works for sums, counts and XOR, where subtraction undoes addition. It does not work as written for the minimum or the maximum, because there is no subtraction for them. A Segment Tree handles those.

To count values, use the tree as a table of counts. In Count of Smaller Numbers After Self (LC 315), replace each value by its rank from 1 to m among the sorted distinct values. Scan nums from the right. For each value with rank r, query(r - 1) is the number of smaller values already seen, and update(r, 1) records this value. For [5, 2, 6, 1] the answers are [2, 1, 1, 0].

The ranks matter because values can be negative or 0. A position of 0 or below would make update loop forever, as in the previous section.

60-Second Pitch

"A prefix-sum array answers a range sum in O(1) time, but one update rewrites up to N entries. A plain scan makes every query O(N). With many updates and many queries, I use a Fenwick tree. tree[i] holds the sum of the i & -i values that end at position i. query adds a block and removes the lowest set bit, so the blocks do not overlap and cover the prefix once. update adds the lowest set bit to reach every block that contains the position. Each loop runs at most about log N times. So update and sumRange are O(log N), and the space is O(N). I keep the 1-based index, and I add the change val - nums[index], not the new value. If the question asks for counts of earlier smaller values, I rank the values first and use the same tree as a table of counts."

Common Questions

Why use a Fenwick tree and not a prefix-sum array?

A prefix-sum array answers sumRange in O(1) time, but one update changes up to N prefix sums, which costs O(N) time. A Fenwick tree makes both operations O(log N). Use the prefix array when the values never change.

Why does the tree start at index 1?

The block of i has i & -i values. For i = 0, that expression is 0, so the block is empty and the loops i += i & -i and i -= i & -i never move. Starting at 1 keeps every block non-empty.

When would you use a Segment Tree instead?

When the query cannot be answered by subtracting two prefixes, for example the minimum or the maximum of a range. A Fenwick tree is shorter to write and uses less memory for sums, counts and XOR. A Segment Tree also supports range updates with lazy values.

How do you build the tree in O(N) time?

Copy each value into tree[i]. Then for each i from 1 to n, add tree[i] into tree[i + (i & -i)] when that position is at most n. Each entry is added into its parent once, so the build makes N additions.

How does a Fenwick tree solve Count of Smaller Numbers After Self (LC 315)?

Replace each value by its rank from 1 to m. Scan from the right. For the value with rank r, the answer is query(r - 1), and then update(r, 1) marks it as seen. Each step is O(log m), so the total is O(N log N).

SWE Reasoning

Spot it

  • The array changes by single-value updates, and queries for prefix or range sums arrive between the updates.
  • A prefix-sum array answers each query in O(1) time, but one update costs O(N) time. A plain scan costs O(N) per query. Many updates and many queries together are too slow.
  • The question counts earlier values that are smaller (or larger) than the current one, which is a prefix count over value ranks.
  • The operation can be undone by subtraction: sum, count or XOR. If it is a minimum or a maximum, use a Segment Tree instead.

The invariant, step by step

  1. tree[i] stores the sum of the block of i & -i values that ends at position i.
  2. query(i) adds tree[i], then sets i -= i & -i. The next block ends just before the current block starts.
  3. So the blocks that query adds do not overlap, and together they cover positions 1 to i once. The loop ends at i = 0.
  4. A change at position p must reach every block that contains p. update starts at p and sets i += i & -i. Each step moves to the next larger block that still contains p.
  5. The loop stops when i is larger than n. Every tree[i] whose block contains p has received the change, and no other entry has.
  6. So the invariant holds after every update, and every query returns the exact prefix sum.

Traps

  • 0-based index. update(index, val) with index = 0 leaves i at 0 and never ends. Pass index + 1.
  • Shifting only right. range_query(left, right + 1) subtracts the prefix up to left - 1, so the result also includes nums[left - 1]. For nums = [1, 3, 5], sumRange(1, 2) returns 9 and not 8.
  • Adding the new value. update adds a change. Passing val instead of val - self.nums[index] adds the whole new value on top of the old one. [1, 3, 5] with update(1, 2) gives 11 and not 8.
  • Raw values as positions. Negative values or 0 break the 1-indexed walk. Replace each value by its rank first.
  • N updates to build. N calls to update cost O(N log N). Use the one-pass build when O(N) is needed.

Practice

Canonical LeetCode:Range Sum Query - Mutable (LC 307)Count of Smaller Numbers After Self (LC 315)Range Sum Query 2D - Mutable (LC 308)Queries on a Permutation With Key (LC 1409)Reverse Pairs (LC 493)Create Sorted Array through Instructions (LC 1649)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•