Algorithm tutorials
Fenwick Tree (Binary Indexed Tree)
A tiny array-based structure for point updates and prefix sums in O(log n). Covers O(n) build, inversion counting over compressed values, k-th smallest via binary lifting, and range-add range-sum with two trees.
Intuition
A prefix-sum array answers "sum of a[l..r]" in O(1), but a single update forces you to rebuild O(n) prefixes. A plain array updates in O(1) but sums in O(n). A Fenwick tree (also called a binary indexed tree, BIT) sits in between: both operations take O(log n), with a tiny array and about ten lines of code.
The trick is to pre-store sums of cleverly chosen blocks so that any prefix splits into at most log2(n) of them. Use 1-based positions and define lowbit(i) = i & -i, the value of the lowest set bit of i (for example, lowbit(12) = 4 because 12 = 1100 in binary). Slot tree[i] stores the sum of the block a[i - lowbit(i) + 1 .. i], a block of length lowbit(i) that ends at i.
- Prefix query: to sum
a[1..i], addtree[i], then jump toi - lowbit(i)(strip the lowest set bit) and repeat until 0. Fori = 7(binary 111) you visit 7, 6 (110) and 4 (100): blocks[7,7],[5,6]and[1,4]. At most one step per set bit, so O(log n). - Point update: to add
deltatoa[i], update every block that containsi:tree[i], theni + lowbit(i), and so on whilei <= n. Each jump moves to a block at least twice as long, so again O(log n).
Range sums come from two prefixes: sum(a[l..r]) = prefix(r) - prefix(l - 1). That subtraction is the main limitation: a plain Fenwick tree needs an invertible operation (sum, XOR, product modulo a prime). Range minimum or maximum with arbitrary updates is segment tree territory.
The real power in interviews comes from building the tree over values instead of positions: keep count[v] = how many times value v has appeared. Then prefix(v) answers "how many elements seen so far are at most v?", which turns many counting problems into a left-to-right (or right-to-left) scan.
When to use it in interviews / OAs
- Point updates mixed with range sums: a mutable range sum query, running totals under edits.
- Counting pairs by order: inversion count, "count of smaller numbers after self", reverse pairs (
a[i] > 2 * a[j]), "count of range sum" in a window of values. Scan the array and query a BIT over (compressed) values. - Dynamic order statistics: a multiset of bounded integers supporting insert, delete, rank and k-th smallest. In Python this is a light replacement for a balanced BST.
- Range add: range add + point query uses one BIT on the difference array; range add + range sum uses two BITs.
- Offline sweeps: sort queries by one coordinate and keep a BIT over the other, for example counting points inside rectangles, or distinct values in subarrays (sort queries by right end, keep only the last occurrence of each value).
- 2D grids with updates: a 2D BIT (a BIT of BITs) answers submatrix sums in O(log R * log C) per operation.
Template
The core structure with an O(n) build. Public indices are 0-based and ranges are half-open like Python slices; the internal array is 1-based because lowbit(0) = 0 would loop forever.
class Fenwick:
# Point add + prefix sums. Public indices are 0-based; tree slots are 1-based.
def __init__(self, values: list[int]) -> None:
self.n = len(values)
self.tree = [0] + list(values)
for i in range(1, self.n + 1): # O(n) build: push each slot into its parent once.
j = i + (i & -i)
if j <= self.n:
self.tree[j] += self.tree[i]
def add(self, i: int, delta: int) -> None:
i += 1
while i <= self.n:
self.tree[i] += delta
i += i & -i # Climb to the next block that also covers position i.
def prefix_sum(self, r: int) -> int:
# Sum of values[0:r].
s = 0
while r > 0:
s += self.tree[r]
r -= r & -r # Drop the lowest set bit: jump to the previous block.
return s
def range_sum(self, l: int, r: int) -> int:
# Sum of values[l:r].
return self.prefix_sum(r) - self.prefix_sum(l)
fw = Fenwick([5, 1, 4, 2, 3])
assert fw.range_sum(1, 4) == 7
fw.add(2, 10) # values[2] becomes 14.
assert fw.range_sum(0, 5) == 25 and fw.prefix_sum(3) == 20
Building by n separate add calls costs O(n log n). The loop in __init__ is O(n): after slot i is complete, add it into its parent i + lowbit(i).
BIT over values. Count, for every element, how many smaller elements appear to its right. Scan from the right; before inserting nums[i], ask how many of the already inserted values have a smaller rank. Large or negative values are first mapped to ranks 1..m (coordinate compression).
def count_smaller_after(nums: list[int]) -> list[int]:
# For each i, how many j > i have nums[j] < nums[i].
ranks = {v: r for r, v in enumerate(sorted(set(nums)), start=1)} # Coordinate compression to 1..m.
m = len(ranks)
tree = [0] * (m + 1) # tree counts how many of each rank we have seen.
out = [0] * len(nums)
for i in range(len(nums) - 1, -1, -1): # Scan right to left: the BIT holds elements to the right.
r = ranks[nums[i]]
j, s = r - 1, 0 # Count ranks 1..r-1, i.e. strictly smaller values.
while j > 0:
s += tree[j]
j -= j & -j
out[i] = s
while r <= m:
tree[r] += 1
r += r & -r
return out
assert count_smaller_after([5, 2, 6, 1]) == [2, 1, 1, 0]
assert sum(count_smaller_after([3, 1, 2])) == 2 # Total = number of inversions.
Ordered multiset with k-th smallest. Binary search over prefix sums would take O(log^2 n). Binary lifting does it in O(log n): build the answer position bit by bit from the highest power of two. Because pos only contains larger powers of two so far, tree[pos + step] is exactly the count of the slots pos + 1 .. pos + step, so each step is one array read.
class BITMultiset:
# Multiset of integers in [0, size) with rank and k-th smallest in O(log size).
def __init__(self, size: int) -> None:
self.n = size
self.tree = [0] * (size + 1)
self.total = 0
def add(self, x: int, cnt: int = 1) -> None: # Use cnt = -1 to remove one copy.
self.total += cnt
i = x + 1
while i <= self.n:
self.tree[i] += cnt
i += i & -i
def count_less(self, x: int) -> int:
s, i = 0, x
while i > 0:
s += self.tree[i]
i -= i & -i
return s
def kth(self, k: int) -> int:
# k-th smallest (1-based) by binary lifting; assumes 1 <= k <= total.
pos = 0
step = 1 << self.n.bit_length()
while step:
nxt = pos + step
if nxt <= self.n and self.tree[nxt] < k: # tree[nxt] is exactly the count of slots pos+1..nxt.
pos = nxt
k -= self.tree[nxt]
step >>= 1
return pos # Slot pos + 1 holds the answer, i.e. value pos.
ms = BITMultiset(10)
for x in (7, 3, 3, 9):
ms.add(x)
assert ms.kth(1) == 3 and ms.kth(2) == 3 and ms.kth(3) == 7 and ms.count_less(7) == 2
ms.add(3, -1)
assert ms.kth(2) == 7 and ms.total == 3
Range add, range sum. Write a as prefix sums of a difference array d. Adding v to a range changes only two entries of d, and the sum of a[1..p] expands to (p + 1) * sum(d[1..p]) - sum(i * d[i] for i in 1..p), so two BITs (one over d[i], one over i * d[i]) give both operations in O(log n).
class RangeAddRangeSum:
# Two BITs over the difference array d: a[1..p] sums to (p + 1) * sum(d) - sum(i * d[i]).
def __init__(self, n: int) -> None:
self.n = n
self.t1 = [0] * (n + 1) # Sums of d[i].
self.t2 = [0] * (n + 1) # Sums of d[i] * i.
def _add(self, i: int, v: int) -> None: # 1-based point add on d.
w = v * i
while i <= self.n:
self.t1[i] += v
self.t2[i] += w
i += i & -i
def _prefix(self, p: int) -> int: # Sum of a[1..p], 1-based.
s1 = s2 = 0
i = p
while i > 0:
s1 += self.t1[i]
s2 += self.t2[i]
i -= i & -i
return (p + 1) * s1 - s2
def range_add(self, l: int, r: int, v: int) -> None: # a[l:r] += v (0-based, half-open).
self._add(l + 1, v)
self._add(r + 1, -v)
def range_sum(self, l: int, r: int) -> int: # Sum of a[l:r].
return self._prefix(r) - self._prefix(l)
ra = RangeAddRangeSum(5)
ra.range_add(1, 4, 2) # a = [0, 2, 2, 2, 0]
ra.range_add(0, 2, 5) # a = [5, 7, 2, 2, 0]
assert ra.range_sum(0, 5) == 16 and ra.range_sum(1, 3) == 9
Complexity
| Operation | Time | Notes |
|---|---|---|
| build | O(n) | O(n log n) if you call add n times |
| point add, prefix/range sum | O(log n) | |
| k-th smallest (binary lifting) | O(log n) | needs a counts BIT |
| range add + range sum | O(log n) | two BITs |
| 2D query/update | O(log R * log C) |
Space is O(n) (or O(number of distinct values) for a BIT over compressed values).
Common pitfalls
- Index 0.
i & -iis 0 wheniis 0, so a 0-based loop never ends. Shift every index by one inside the structure. - Assignments vs increments. The tree stores sums of deltas. To set
a[i] = x, keep a copy ofaand calladd(i, x - a[i]). - Non-invertible operations. Range min/max cannot be computed as a difference of prefixes. (A BIT can keep prefix maxima if values only ever increase, which is enough for LIS-style DPs, but not for general updates.)
- Compression mistakes. Compress every value that can be queried or inserted, including values that appear only in queries, and decide whether you need
rank - 1(strictly smaller) orrank(smaller or equal). - k-th out of range. Binary lifting assumes
1 <= k <= total; check the total count first. - Overflow in Java/C++. Pair counts can reach about n^2 / 2; use 64-bit integers.
Interview variations
- Range sum query, mutable (1D and 2D).
- Count of smaller numbers after self, global inversions, reverse pairs, count of range sum (BIT over compressed prefix sums).
- Create sorted array through instructions: the cost of each insert is
min(count less, count greater). - Sliding window median or any "k-th smallest in a changing window" with values compressed in advance: add and remove counts, then ask
kth. - Longest increasing subsequence variants with a BIT that stores prefix maxima (and counts, for "number of LIS") over compressed values.
- Offline distinct-count queries sorted by right endpoint, keeping only the latest occurrence of each value in the BIT.