Chapter 29: Fenwick Trees and Segment Trees
29.07Intermediate / Advanced5 topics13 min2 interactive animations

Practical decisions

How to choose between a Fenwick tree and a segment tree, what each costs in memory, how to write the iterative version, how to convert indices, and how to test range boundaries.

Learning goals

  • Choose the right structure for an operation and a set of queries
  • Estimate the array sizes of each structure
  • Write a bottom-up segment tree that is correct for non-commutative operations
  • Convert between 0-based, 1-based and half-open indices without off-by-one bugs

Prerequisites

Fenwick treeSegment tree fundamentalsLazy propagation

Most bugs in range-query code are not in the algorithm but at the edges: an index that is off by one, a range that is empty, a tree that is one cell too small. This lesson collects the decisions and the tests that prevent them.

29.07.1

Fenwick vs segment tree

Use a Fenwick tree when the operation has an inverse (sum, XOR, counting) and you only need point updates with prefix or range queries. It is shorter, faster in practice, and uses n + 1 cells. Use a segment tree when the operation has no inverse (minimum, maximum, GCD), when you need range updates with lazy propagation, or when the node must store a structure (maximum subarray, sorted lists).

For range add with range sum, a Fenwick pair works and is simple; for range assign, or add with minimum, you need a lazy segment tree. When in doubt, ask two questions: can the answer for a range be computed from the answer for the prefixes (then Fenwick), and does an update change the whole range at once (then lazy)?

NeedFenwick treeSegment tree
Point update + prefix/range sumyes, simplestyes
Range min / max / GCDno (no inverse)yes
Range add + range sumyes (two trees)yes (lazy)
Range assignnoyes (lazy)
k-th element by descentyesyes
Node holds a structure (tuple, list)noyes
Memoryn + 1 cells2n to 4n cells
Code lengthabout 10 linesabout 40 lines

Fenwick tree (binary indexed tree): build, update, prefix and range sums

Interactive · edit the input

Your input

Try

a[i], i = 1..n

3i=1
2i=2
-1i=3
6i=4
5i=5
4i=6
-3i=7
3i=8

bit[i] = sum of a over the range shown under it

31: 1..1
52: 1..2
-13: 3..3
104: 1..4
55: 5..5
96: 5..6
-37: 7..7
198: 1..8
Setup

The Fenwick tree for a = [3, 2, -1, 6, 5, 4, -3, 3]. bit[i] holds the sum of the lowbit(i) values ending at position i (shown under each cell). Positions are 1-based: index 5 of the input is i = 6.

Step 1 / 4
Java
1int[] bit = new int[n + 1]; // 1-based: bit[i] covers the lowbit(i) values ending at i
2void update(int i, int delta) { // a[i] += delta, i is 1-based
3 for (; i <= n; i += i & -i) bit[i] += delta; // climb: add the lowest set bit
4}
5int prefix(int i) { // sum of a[1..i]
6 int s = 0;
7 for (; i > 0; i -= i & -i) s += bit[i]; // descend: strip the lowest set bit
8 return s;
9}
10int range(int l, int r) { return prefix(r) - prefix(l - 1); }
11void buildLinear(int[] a) { // O(n) build
12 for (int i = 1; i <= n; i++) {
13 bit[i] += a[i];
14 int j = i + (i & -i); // the next cell that covers i
15 if (j <= n) bit[j] += bit[i];
16 }
17}

Live state

0 cells touched

n

8

No condition evaluated on this step
Execution trace (dry run table)Show 4 steps
StepLineStateWhat happens
1L1n=8The Fenwick tree for a = [3, 2, -1, 6, 5, 4, -3, 3]. bit[i] holds the sum of the lowbit(i) values ending at position i (shown under each cell). Positions are 1-based: index 5 of the input is i = 6.
2L7i=6 binary=110 i & -i=2 sum=9prefix(6): add bit[6] = 9 (covers 5..6). Next i = 6 - 2 = 4.
3L7i=4 binary=100 i & -i=4 sum=19prefix(6): add bit[4] = 10 (covers 1..4). Next i = 4 - 4 = 0: done.
4L8return=19 cells touched=2prefix(6) = 19: the sum of a[1..6] from 2 cells, because the ranges of the visited cells tile 1..6 exactly.
29.07.2

Space costs

A Fenwick tree needs n + 1 cells. A recursive segment tree has 2n - 1 nodes but, stored by heap index, needs an array big enough for the largest index in use. Sizing it as 4n is always safe. A tighter safe size is twice the next power of two above n, which is at most 4n - 2 and often much smaller. A bottom-up segment tree needs exactly 2n cells, because leaves go at n..2n-1.

Lazy trees need extra arrays for the tags (one per tag field), and merge-sort trees need O(n log n) cells. Persistent trees add O(log n) nodes per update and dynamic trees O(log size) nodes per touched position. When memory is tight, use the smallest layout that supports your operations: 2n iterative, then 2 × next power of two recursive, then 4n.

StructureCells for n valuesNotes
Fenwick treen + 1index 0 unused
Iterative segment tree2nleaves at n..2n-1
Recursive, tight2 × next power of two ≥ nat most 4n - 2
Recursive, safe4nthe usual choice
Lazy segment treerecursive size × (1 + tag fields)sum plus the tags
Merge-sort treen log n valuessorted list per node
Java
1static int nextPowerOfTwo(int n) { return n <= 1 ? 1 : Integer.highestOneBit(n - 1) << 1; }
2static int recursiveTreeSize(int n) { return 2 * nextPowerOfTwo(n); } // enough for every heap index of the recursive split
3static int iterativeTreeSize(int n) { return 2 * n; }
4static int fenwickSize(int n) { return n + 1; }
29.07.3

Iterative segment tree

The **bottom-up** segment tree stores the leaves at positions n..2n-1 and every inner node i as the combination of 2i and 2i + 1. A point update sets the leaf and walks up by halving the index. A range query on the half-open interval [l, r) moves both ends up one level at a time: if l is a right child, its node is part of the answer and l moves right; if r is a right child, the node to its left is part of the answer. There is no recursion and the array has exactly 2n cells.

For a non-commutative operation keep two accumulators: the nodes taken at the left end go to the left result in increasing position order, and the nodes taken at the right end are prepended to the right result. The final answer is left combined with right. Compare with the animation's "Iterative sum" preset: the query [1, 4] on 2 5 1 4 9 3 takes 3 nodes and returns 19.

Java
1class IterativeSegmentTree {
2 private final int n;
3 private final long[] t;
4 private final LongBinaryOperator op;
5 private final long identity;
6 IterativeSegmentTree(int[] a, LongBinaryOperator op, long identity) {
7 this.n = a.length; this.op = op; this.identity = identity;
8 t = new long[2 * n];
9 for (int i = 0; i < n; i++) t[n + i] = a[i];
10 for (int i = n - 1; i >= 1; i--) t[i] = op.applyAsLong(t[2 * i], t[2 * i + 1]);
11 }
12 void set(int i, long v) {
13 for (t[i += n] = v; i > 1; i >>= 1) t[i >> 1] = op.applyAsLong(t[i & ~1], t[i | 1]); // left child first
14 }
15 long query(int l, int r) { // half-open [l, r); correct for non-commutative operations
16 long left = identity, right = identity;
17 for (l += n, r += n; l < r; l >>= 1, r >>= 1) {
18 if ((l & 1) == 1) left = op.applyAsLong(left, t[l++]); // append on the left side
19 if ((r & 1) == 1) right = op.applyAsLong(t[--r], right); // prepend on the right side
20 }
21 return op.applyAsLong(left, right);
22 }
23}

Segment tree: build, range query, point update, iterative version

Interactive · edit the input

Your input

Try

segment tree (each node shows its range under the value) · 11 nodes

?

[0, 5]

?

[0, 2]

?

[0, 1]

?

[0, 0]

?

[1, 1]

?

[2, 2]

?

[3, 5]

?

[3, 4]

?

[3, 3]

?

[4, 4]

?

[5, 5]

Partly inside: recurseFully inside: use itOutside: neutral elementJust built or recomputedCandidate

a[]

20
51
12
43
94
35
Setup

Build a sum tree over 6 values: each node stores the sum of its range; a leaf stores one element. ? marks nodes not built yet.

Step 1 / 27
Java
1void build(int node, int lo, int hi) { // recursive segment tree
2 if (lo == hi) { tree[node] = a[lo]; return; } // leaf
3 int mid = lo + (hi - lo) / 2;
4 build(2 * node, lo, mid);
5 build(2 * node + 1, mid + 1, hi);
6 tree[node] = tree[2 * node] + tree[2 * node + 1]; // combine
7}
8int query(int node, int lo, int hi, int l, int r) {
9 if (r < lo || hi < l) return 0; // disjoint: the neutral element
10 if (l <= lo && hi <= r) return tree[node]; // fully inside: use the stored value
11 int mid = lo + (hi - lo) / 2;
12 return query(2 * node, lo, mid, l, r) + query(2 * node + 1, mid + 1, hi, l, r);
13}
14void update(int node, int lo, int hi, int i, int v) {
15 if (lo == hi) { tree[node] = v; return; }
16 int mid = lo + (hi - lo) / 2;
17 if (i <= mid) update(2 * node, lo, mid, i, v); else update(2 * node + 1, mid + 1, hi, i, v);
18 tree[node] = tree[2 * node] + tree[2 * node + 1]; // recompute on the way back
19}
20int queryIterative(int l, int r) { // bottom-up, half-open [l, r)
21 int res = 0;
22 for (l += n, r += n; l < r; l >>= 1, r >>= 1) {
23 if ((l & 1) == 1) res += tree[l++]; // l is a right child: take it and move right
24 if ((r & 1) == 1) res += tree[--r]; // r is exclusive: its left neighbour is a right child
25 }
26 return res;
27}

Live state

0 nodes visited

n

6

No condition evaluated on this step
Execution trace (dry run table)Show 27 steps
StepLineStateWhat happens
1L1n=6Build a sum tree over 6 values: each node stores the sum of its range; a leaf stores one element. ? marks nodes not built yet.
2L2node=8 range=[0, 0] value=2Leaf [0]: tree[8] = a[0] = 2.
3L2node=9 range=[1, 1] value=5Leaf [1]: tree[9] = a[1] = 5.
4L6node=4 range=[0, 1] value=7Combine the children of [0, 1]: 2 + 5 = 7.
5L2node=5 range=[2, 2] value=1Leaf [2]: tree[5] = a[2] = 1.
6L6node=2 range=[0, 2] value=8Combine the children of [0, 2]: 7 + 1 = 8.
7L2node=12 range=[3, 3] value=4Leaf [3]: tree[12] = a[3] = 4.
8L2node=13 range=[4, 4] value=9Leaf [4]: tree[13] = a[4] = 9.
9L6node=6 range=[3, 4] value=13Combine the children of [3, 4]: 4 + 9 = 13.
10L2node=7 range=[5, 5] value=3Leaf [5]: tree[7] = a[5] = 3.
11L6node=3 range=[3, 5] value=16Combine the children of [3, 5]: 13 + 3 = 16.
12L6node=1 range=[0, 5] value=24Combine the children of [0, 5]: 8 + 16 = 24.
13L8l=1 r=4Query sum(a[1..4]). Each node is outside (return 0, the neutral element), fully inside (use its stored value) or partly inside (ask both children and combine).
14L11node=1 range=[0, 5] kind=partial[0, 5] overlaps [1, 4] only partly: recurse into both children.
15L11node=2 range=[0, 2] kind=partial[0, 2] overlaps [1, 4] only partly: recurse into both children.
16L11node=4 range=[0, 1] kind=partial[0, 1] overlaps [1, 4] only partly: recurse into both children.
17L9node=8 range=[0, 0] kind=disjoint[0, 0] lies outside [1, 4]: return 0.
18L10node=9 range=[1, 1] kind=inside[1, 1] lies inside [1, 4]: use the stored value 5; no need to look at its children.
19L12node=4 result=5Combine the two answers below [0, 1]: 5.
20L10node=5 range=[2, 2] kind=inside[2, 2] lies inside [1, 4]: use the stored value 1; no need to look at its children.
21L12node=2 result=6Combine the two answers below [0, 2]: 6.
22L11node=3 range=[3, 5] kind=partial[3, 5] overlaps [1, 4] only partly: recurse into both children.
23L10node=6 range=[3, 4] kind=inside[3, 4] lies inside [1, 4]: use the stored value 13; no need to look at its children.
24L9node=7 range=[5, 5] kind=disjoint[5, 5] lies outside [1, 4]: return 0.
25L12node=3 result=13Combine the two answers below [3, 5]: 13.
26L12node=1 result=19Combine the two answers below [0, 5]: 19.
27L12return=19 nodes visited=9sum(a[1..4]) = 19, after visiting 9 nodes. At most four nodes per level are visited, so a query is O(log n).
29.07.4

Index conversions

Three conventions meet in this chapter: Fenwick trees are 1-based, most Java arrays and segment trees are 0-based, and the iterative segment tree and String.substring use half-open ranges [l, r). Convert once, at the boundary, and keep everything inside the structure in one convention. A wrapper class that exposes 0-based inclusive ranges, as most problem statements do, removes the whole class of off-by-one bugs from the calling code.

The usual conversions: 0-based index i is Fenwick position i + 1; an inclusive [l, r] is the half-open [l, r + 1); and sum(l..r) = prefix(r + 1) - prefix(l) when prefix(e) means the sum of the first e elements. With these, l = 0 needs no special case, because prefix(0) is an empty sum.

ConventionRange of 3 elements starting at index 2Fenwick position of the first
0-based, inclusive[2, 4]3
0-based, half-open[2, 5)3
1-based, inclusive[3, 5]3
Java
1class ZeroBasedFenwick { // 0-based, inclusive ranges, like most problem statements
2 private final long[] tree;
3 ZeroBasedFenwick(int n) { tree = new long[n + 1]; }
4 void add(int index, long delta) {
5 for (int i = index + 1; i < tree.length; i += i & -i) tree[i] += delta; // 0-based -> 1-based
6 }
7 long prefix(int endExclusive) { // sum of [0, endExclusive)
8 long s = 0;
9 for (int i = endExclusive; i > 0; i -= i & -i) s += tree[i];
10 return s;
11 }
12 long sum(int l, int r) { return prefix(r + 1) - prefix(l); } // inclusive [l, r]
13}
29.07.5

Testing range boundaries

Range structures break at the edges, so test those on purpose, against a brute-force array. The important cases: a single element (l == r), the first element, the last element, the whole array, n = 1, n a power of two and n one more or one less than a power of two, updates at the first and last index, and an update that does not change the value. Then add randomised operations: random updates and random ranges, compared with a plain loop after every step.

The harness below drives any structure that implements a tiny interface, so the same test checks a Fenwick wrapper, a segment tree or a lazy tree. Failures print the first operation that disagrees, which is usually enough to see the bug. Run it for several sizes, not only one: many bugs appear only when n is odd or a power of two.

Java
1interface RangeSum {
2 void set(int i, long v); // a[i] = v, 0-based
3 long sum(int l, int r); // inclusive, 0-based
4}
5static String stress(RangeSum fast, int n, int rounds, long seed) {
6 Random rnd = new Random(seed);
7 long[] a = new long[n];
8 for (int round = 0; round < rounds; round++) {
9 int i = rnd.nextInt(n);
10 long v = rnd.nextInt(2001) - 1000;
11 a[i] = v;
12 fast.set(i, v);
13 int l = rnd.nextInt(n), r = rnd.nextInt(n);
14 if (l > r) { int t = l; l = r; r = t; }
15 int[][] ranges = {{l, r}, {0, n - 1}, {0, 0}, {n - 1, n - 1}, {i, i}}; // random, whole, first, last, changed
16 for (int[] q : ranges) {
17 long want = 0;
18 for (int k = q[0]; k <= q[1]; k++) want += a[k];
19 if (fast.sum(q[0], q[1]) != want)
20 return "sum(" + q[0] + ", " + q[1] + ") after set(" + i + ", " + v + "): expected " + want;
21 }
22 }
23 return "ok";
24}

Practice ladder

Now try it yourself

Easy

  • Range Sum Query - Mutable

Medium

  • Range Sum Query 2D - Mutable
  • Count of Smaller Numbers After Self

Advanced

  • Count of Range Sum
  • Longest Increasing Subsequence II
  • Falling Squares

Key takeaways

  • Fenwick: invertible operation, point update, prefix/range query. Segment tree: everything else.
  • Sizes: Fenwick n + 1, iterative segment tree 2n, recursive segment tree 4n (or 2 × next power of two).
  • Convert indices once at the boundary, and test single, first, last and whole-array ranges against a brute force.