leetcode / Beyond the interview / Fenwick trees advanced

Fenwick trees

A Fenwick tree, or binary indexed tree, keeps running totals of an array that changes. It is a single list the same length as the array, where each slot holds the sum of a block whose length is the lowest set bit of the slot's index. A prefix sum adds up a few of those blocks, and an update fixes the few blocks that contain the changed position, each in O(log n) with a three-line loop. It rarely comes up in interviews: no problem on the four lists needs it. It is here as the shortest way to make prefix sums survive updates.

On this page
  1. Problem
  2. Intuition
  3. Mechanics
  4. The lowest set bit
  5. The invariant
  6. Worked example
  7. Implementation
  8. Variations and edge cases
  9. Complexity and tradeoffs
  10. Problems
  11. Connections
  12. Read first
  13. Read next
  14. Check yourself
  15. References

Problem

Prefix sums are perfect until the array changes, and then one update rewrites every prefix after it. A segment tree fixes that, but it needs 2n to 4n slots and 30 or more lines. When the question is only ever "sum of the first i values" or "sum of a range" with point updates, or anything else built from an operation that can be undone, a Fenwick tree does the same job in n + 1 slots and about ten lines.

SignalPatternWhy
Prefix or range sums, with point updates in betweenFenwick treeBoth operations walk at most log2 n slots by changing one bit of the index at a time.
"How many earlier (or later) values are smaller than this one?"Fenwick tree over value ranksRecord each value at its rank; counting smaller values seen so far is a prefix sum over ranks.
"Add v to every element in [l, r]" and "what is element i?"Fenwick tree over the difference arrayA range add is two point updates to the differences, and an element is a prefix sum of them.
Range minimum or maximum with updatesSegment treeA Fenwick tree gets a range by subtracting two prefixes, and min and max cannot be subtracted.
Range sums on an array that never changesPrefix sumsA plain prefix array answers in O(1) and builds in O(n); the tree only pays off once updates arrive.

Intuition

Every positive whole number is a sum of distinct powers of two, the ones its binary form has set. Seven is 4 + 2 + 1. So the first seven values of an array split into a block of 4, then a block of 2, then a block of 1: positions 1 to 4, 5 to 6, and 7. If every block of that kind were already summed and stored, any prefix would cost one addition per set bit.

A Fenwick tree stores exactly those blocks. Slot i holds the block that ends at position i and whose length is i's lowest set bit: slot 6 (110 in binary) holds positions 5 to 6, slot 4 (100) holds 1 to 4, slot 7 (111) holds just 7. Reading a prefix means starting at slot i and repeatedly dropping the lowest set bit to jump to the block just before. Updating position i means fixing every block that covers it, which you reach by repeatedly adding the lowest set bit.

The Fenwick tree over 5, 8, 6, 3, 2, 7, 2, 6, reading the prefix sum of the first 7 values Each slot is drawn as a bar over the positions it sums: t[1] covers 1 to 1 and holds 5; t[2] covers 1 to 2 and holds 13; t[3] covers 3 to 3 and holds 6; t[4] covers 1 to 4 and holds 22; t[5] covers 5 to 5 and holds 2; t[6] covers 5 to 6 and holds 9; t[7] covers 7 to 7 and holds 2; t[8] covers 1 to 8 and holds 39. The prefix of 7 adds t[7], t[6], t[4], which together cover positions 1 to 7, for a total of 33. t[1] · 0001 5 t[2] · 0010 13 t[3] · 0011 6 t[4] · 0100 22 t[5] · 0101 2 t[6] · 0110 9 t[7] · 0111 2 t[8] · 1000 39 5 i = 1 8 i = 2 6 i = 3 3 i = 4 2 i = 5 7 i = 6 2 i = 7 6 i = 8 prefix(7) = t[7] + t[6] + t[4] = 2 + 9 + 22 = 33

Bars sit on rows by block length: length 8 on top, then 4, 2 and 1. The green bars are the slots a prefix of 7 reads, and they tile positions 1 to 7 with no gap or overlap. The diagram scrolls sideways on a narrow screen.

Mechanics

The lowest set bit

i & -i keeps only the lowest set bit of i: in two's complement, -i flips every bit above it and keeps it, so the AND leaves that one bit (the trick from bit manipulation). Write lowbit(i) for it. Indices start at 1, because 0 has no set bit and would loop forever.

The invariant

Slot i holds the sum of positions i − lowbit(i) + 1 through i. Everything else follows from that one sentence.

  • Prefix. Slot i covers the top lowbit(i) positions of [1, i]. What remains is [1, i − lowbit(i)], so jump to i − lowbit(i) and repeat until 0. Each jump clears one set bit, so there are at most as many steps as i has set bits, at most ⌊log2 n⌋ + 1.
  • Update. The slots whose block contains position i are i itself, then i + lowbit(i), then that plus its lowbit, and so on. Adding the lowbit carries into a strictly higher bit, so the lowbit at least doubles each step and there are at most log2 n + 1 steps. Each of those blocks starts at or before i, and no slot skipped over can contain i: any j between two consecutive stops has a lowbit too small to reach back to i.
  • Range. The sum of [left, right] is prefix(right) − prefix(left − 1). This needs subtraction, which is why the plain tree handles sums, counts and XOR but not min or max.

Worked example

The slots over [5, 8, 6, 3, 2, 7, 2, 6].

Slot iBinarylowbit(i)CoversStored sum
1000111..15
2001021..213
3001113..36
4010041..422
5010115..52
6011025..69
7011117..72
8100081..839

Prefix sum of the first 7. Strip the lowest set bit each step:

iBinaryAdds t[i]CoversRunning totalNext i = i − lowbit(i)
7011127..726
6011095..6114
40100221..4330

The prefix of 2 is 13, read from 1 slot, so the sum of positions 3 to 7 is 33 − 13 = 20.

Add 7 at position 3. Add the lowest set bit each step:

iBinaryCoversOld t[i]New t[i]Next i = i + lowbit(i)
300113..36134
401001..422298
810001..8394616 (past the end, stop)

3 of 8 slots change, and the array is now [5, 8, 13, 3, 2, 7, 2, 6].

Adding 7 at position 3 updates every slot whose block contains position 3 The update climbs t[3] from 6 to 13, then t[4] from 22 to 29, then t[8] from 39 to 46. Those are exactly the bars that lie over position 3; every other slot keeps its value. t[1] · 0001 5 t[2] · 0010 13 t[3] · 0011 6 → 13 t[4] · 0100 22 → 29 t[5] · 0101 2 t[6] · 0110 9 t[7] · 0111 2 t[8] · 1000 39 → 46 5 i = 1 8 i = 2 6 i = 3 3 i = 4 2 i = 5 7 i = 6 2 i = 7 6 i = 8 i = 3 → 4 → 8: each step adds the lowest set bit

The amber bars are the slots an update at position 3 changes: every bar that sits over position 3, and no other.

Counting smaller values to the right of each element in [3, 1, 4, 1, 5, 9, 2, 6]. Ranks compress the values to 1→1, 2→2, 3→3, 4→4, 5→5, 6→6, 9→7. Walking from the right, each element asks for the prefix sum up to its rank minus 1, then adds 1 at its rank:

IndexValueRankSmaller to its right: prefix(rank − 1)Values recorded after this step
76606
62202, 6
59722, 6, 9
45512, 5, 6, 9
31101, 2, 5, 6, 9
24421, 2, 4, 5, 6, 9
11101, 1, 2, 4, 5, 6, 9
03331, 1, 2, 3, 4, 5, 6, 9

The counts in original order are [3, 0, 2, 0, 1, 2, 0, 0].

Implementation

class Fenwick:
    def __init__(self, n):
        self.tree = [0] * (n + 1)

    def add(self, i, delta):
        while i < len(self.tree):
            self.tree[i] += delta
            i += i & -i

    def prefix(self, i):
        total = 0
        while i > 0:
            total += self.tree[i]
            i -= i & -i
        return total

    def range_sum(self, left, right):
        return self.prefix(right) - self.prefix(left - 1)

Positions are 1-based. To start from an existing array, call add(i, nums[i - 1]) for every i, which costs O(n log n), or use the linear build below. To set a position to a new value rather than add to it, add new − old and keep the array alongside the tree.

Variations and edge cases

Linear build. Each slot pushes its finished total into the one slot directly above it, its parent i + lowbit(i). Going left to right, a slot is complete before it is pushed:

def build(nums):
    tree = [0] + list(nums)
    for i in range(1, len(tree)):
        parent = i + (i & -i)
        if parent < len(tree):
            tree[parent] += tree[i]
    return tree

Range add, point query. Store the differences instead of the values. Adding v to [l, r] is add(l, v) and add(r + 1, -v) (skip the second when r is the last position), and the current value at i is prefix(i), the same move as a difference array in prefix sums.

Counting smaller values. The worked example's loop:

def count_smaller(nums):
    ranks = {value: rank for rank, value in enumerate(sorted(set(nums)), 1)}
    seen = Fenwick(len(ranks))
    counts = [0] * len(nums)
    for index in range(len(nums) - 1, -1, -1):
        rank = ranks[nums[index]]
        counts[index] = seen.prefix(rank - 1)
        seen.add(rank, 1)
    return counts

Walk from the left and ask index − seen.prefix(rank) for "how many earlier values are larger", which counts inversions.

Find the k-th smallest. When the tree stores counts (all non-negative), descend by powers of two: try each block from the largest down and step over it whenever its count is still below k. It returns the smallest position whose prefix reaches k in O(log n), without a binary search over prefix:

def find_kth(tree, k):
    pos, step = 0, 1 << (len(tree) - 1).bit_length()
    while step:
        if pos + step < len(tree) and tree[pos + step] < k:
            pos += step
            k -= tree[pos]
        step //= 2
    return pos + 1
  • Index 0. add(0, v) loops forever, since 0 & -0 is 0. Shift 0-based positions by one on the way in.
  • Ranks must start at 1 for the same reason, and prefix(rank − 1) then cleanly returns 0 for the smallest value.
  • Equal values. "Strictly smaller" asks prefix(rank − 1); "smaller or equal" asks prefix(rank). Duplicates share one rank.
  • Negative values in the array are fine for sums, but find_kth needs non-negative counts, because the descent assumes prefixes only grow.

Complexity and tradeoffs

add and prefix are O(log n), building is O(n) or O(n log n), and the tree uses n + 1 slots. The bounds, checked by trying every position:

nMost slots one prefix readsMost slots one update writesBits in n
8344
13344
100677
100091010
  • Brute-force baseline. A plain array updates in O(1) but sums a range in O(n), so q queries cost O(qn). Prefix sums flip that: O(1) queries, O(n) updates. The tree makes both O(log n).
  • Against a segment tree. Same O(log n), a quarter of the memory, a fraction of the code and smaller constants. But it only answers ranges through subtraction, so range min and max, and range updates with range queries, need the segment tree (or two Fenwick trees for the sum case).
  • Against prefix sums. With no updates, plain prefix sums answer in O(1). The tree is for when updates and queries interleave.
  • Against merge sort. Counting inversions or smaller-after-self also falls out of merge sort in O(n log n), with no tree and no rank compression. The Fenwick version wins when the values arrive one at a time.
  • In an interview. It is short enough to write from memory, which makes it the practical choice whenever a sum-only problem needs updates.

Problems

No problem on the four lists has this page as its home, which is why it sits under Beyond the interview.

For practice beyond the lists, in rising difficulty:

Connections

Read first

Read next

No page builds on this one yet.

A Fenwick tree is prefix sums with the work spread across the binary digits of the index, and its two loops are the lowest-set-bit trick from bit manipulation. It does a subset of what a segment tree does, more cheaply. Counting smaller values over ranks solves the same problems as a merge-sort count or a BST with subtree sizes, and a max-version over ranks speeds up LIS-style DPs, since "best answer among smaller values" is a prefix max that only ever grows.

Check yourself

Which slots does prefix(7) read in the example, and why do they never overlap?

t[7] covering 7..7, t[6] covering 5..6, t[4] covering 1..4. Each step's block ends exactly where the previous block began, because the next index is the current one minus the current block's length.

Why does add visit at most about log2 n slots?

Adding the lowest set bit carries it into a strictly higher position, so lowbit at least doubles every step. After about log2 n doublings the index passes n.

Why can't a plain Fenwick tree answer "minimum of positions 3 to 7"?

It only stores blocks that end at slot indices, and a general range is answered as one prefix minus another. Nothing can be taken away from a minimum, so the prefix of 2 cannot be removed from the prefix of 7.

In the counting example, why walk from the right, and why ask for prefix(rank − 1) rather than prefix(rank)?

Walking from the right means the tree holds exactly the elements after the current one. Asking up to rank − 1 counts strictly smaller values, so an equal value further right, like the second 1, is not counted.

References

  • Fenwick, P. M., "A new data structure for cumulative frequency tables", Software: Practice and Experience 24(3):327–336, 1994: the original paper.
  • Ryabko, B. Ya., "A fast on-line adaptive code", IEEE Transactions on Information Theory 38(4):1400–1404, 1992: an earlier description of the same structure.
  • Cormen, Leiserson, Rivest and Stein, Introduction to Algorithms, 3rd edition, problem 2-4 (inversions) and chapter 14, "Augmenting Data Structures", for order statistics the same tree answers.
  • Sedgewick and Wayne, Algorithms, 4th edition, section 2.2, for counting inversions with merge sort, the static alternative.
  • Skiena, S., The Algorithm Design Manual, the catalog entry "Range Search", on choosing among range-query structures.
  • The NeetCode roadmap and the Blind 75, Grind 169, NeetCode 150 and NeetCode 250 lists, used as the index of topics and problems.