Segment Trees & Fenwick Trees

Answer range queries on an array that keeps changing, with updates and queries both O(log n) instead of one of them costing O(n).

AdvancedRead first: Prefix Sums, Binary Trees

How It Works

A prefix sum array answers any range sum in O(1), but changing one element shifts every prefix after it, so an update costs O(n). A plain array has the opposite trade: O(1) updates, O(n) sums. When a problem mixes updates and queries, both structures here meet in the middle. Each stores the sums of carefully chosen blocks, so a query adds up O(log n) blocks and an update touches O(log n) blocks.

A segment tree is a binary tree over index ranges. The root covers the whole array, each node splits its range in half between its two children, and each leaf is a single element. Every node stores the sum (or min, max, gcd) of its range. Any query range breaks into O(log n) whole nodes, so the answer is a handful of stored values instead of a scan.

       |----------------- 39 ------------------|
       |------- 22 --------|------- 17 --------|
       |-- 13 ---|--- 9 ---|--- 9 ---|--- 8 ---|
value    5    8    6    3    2    7    2    6
index    0    1    2    3    4    5    6    7
A segment tree over 8 values: each bar stores the sum of the values under it. The sum over indexes 2 to 5 is two stored bars, 9 + 9 = 18, without touching the leaves.

A Segment Tree Query and Update

Step 1 of 14

tree

  • Node handling the call now
  • Passes the call down to its children
  • Answered from its stored sum, or updated
  • No overlap: skipped

Each node stores the sum of the range under it, and the leaves are the values themselves. To answer sumRange(2, 5), start at the root and ask how each node's range overlaps [2,5].

All 14 steps as text
  1. Each node stores the sum of the range under it, and the leaves are the values themselves. To answer sumRange(2, 5), start at the root and ask how each node's range overlaps [2,5].
  2. [0,7] only partly overlaps [2,5], so split the question between its halves, [0,3] and [4,7], and add their answers.
  3. [0,3] only partly overlaps [2,5], so split the question between its halves, [0,1] and [2,3], and add their answers.
  4. [0,1] doesn't overlap [2,5] at all, so it adds nothing. Return 0 without looking below it.
  5. [2,3] is fully inside [2,5], so return its stored sum, 9, without visiting the 2 values under it. That's the speedup.
  6. [4,7] only partly overlaps [2,5], so split the question between its halves, [4,5] and [6,7], and add their answers.
  7. [4,5] is fully inside [2,5], so return its stored sum, 9, without visiting the 2 values under it. That's the speedup.
  8. [6,7] doesn't overlap [2,5] at all, so it adds nothing. Return 0 without looking below it.
  9. sumRange(2, 5) returns 9 + 9 = 18. Two stored sums answered it, and it never touched a leaf.
  10. update(3, 10) walks down [0,7], [0,3], [2,3], [3,3], always into the half that holds index 3. At the leaf it sets 3 to 10.
  11. Back up at [2,3], recompute the sum from its children: 6 + 10 = 16.
  12. Back up at [0,3], recompute the sum from its children: 13 + 16 = 29.
  13. Back up at [0,7], recompute the sum from its children: 29 + 17 = 46. That's one node per level, so the update is O(log n).
  14. sumRange(2, 5) asks the same nodes as before and now gets 16 + 9 = 25. The update fixed every stored sum that covers index 3, so none of them went stale.

A Fenwick tree (also called a binary indexed tree) is the leaner cousin: one array of n + 1 numbers in which slot i holds the sum of a block that ends at i, sized by the lowest set bit of i. It only answers prefix sums, and gets a range sum by subtracting one prefix from another. The template shows how i & -i drives it.

  • Pick a Fenwick tree when the operation can be undone (subtraction undoes a sum or a count, XOR undoes XOR) and you need point updates with prefix or range queries. It's about ten lines of code and n + 1 integers.
  • Pick a segment tree when the operation can't be undone, like range min, max or gcd, since a prefix minimum can't be subtracted away. It also handles updates to a whole range (with lazy propagation) and nodes that merge something richer than a number, such as the best subarray inside their range.
  • Both cost O(log n) per update and per query. The Fenwick tree has smaller constants and less to get wrong; the segment tree is the more general tool.

When to Reach for It

  • An array gets point updates mixed in with range queries (sum, min, max, count).
  • You'd reach for a prefix sum array, except the values change between queries.
  • You process items in order and keep asking how many earlier ones are smaller or larger than the current one, as in counting inversions.
  • Values are huge or negative, but only their relative order matters, so ranks will do.
  • Both n and the number of queries are around 10^5, so O(n) per query is too slow.

Complexity

OperationTimeSpace
Fenwick: point add or prefix sumO(log n)O(n)
Fenwick: range sum (two prefix sums)O(log n)O(n)
Segment tree: buildO(n)O(n): 4n slots
Segment tree: point update or range queryO(log n)O(log n) recursion
Plain prefix sum array, for comparisonO(1) query, O(n) updateO(n)

The Template

A Fenwick Tree

Number the positions from 1 (the code takes 0-based indices and adds one on the way in). Slot i of tree holds the sum of the i & -i positions that end at i. Odd slots hold a single value, and slots with more trailing zeros hold longer runs, nested like the tick marks on a ruler.

i  binary  i & -i  tree[i] sums positions
1  0001    1       1
2  0010    2       1..2
3  0011    1       3
4  0100    4       1..4
5  0101    1       5
6  0110    2       5..6
7  0111    1       7
8  1000    8       1..8

sum of 1..7:  7 -> 6 -> 4 -> 0
              tree[7] + tree[6] + tree[4]
              covers 7, 5..6 and 1..4
add at 3:     3 -> 4 -> 8
              the only blocks that contain 3
Each slot's block length is its lowest set bit. A prefix sum walks down by removing that bit; an update walks up by adding it.

Why `i & -i` is the lowest set bit. Computers store -i in two's complement, which is ~i + 1: flip every bit, then add one. Flipping turns the trailing zeros of i into ones, and the + 1 carries through all of them until it lands on the first zero, which is exactly where i had its lowest 1. Below that spot both numbers are 0, and above it every bit is opposite. So the AND keeps that one bit and nothing else.

     i  =  0 1 1 0 0   12
    ~i  =  1 0 0 1 1   every bit flipped
    -i  =  1 0 1 0 0   ~i + 1 (two's complement)
i & -i  =  0 0 1 0 0   4, the lowest set bit

prefixSum starts at i and keeps subtracting the lowest set bit, collecting one block per step until it reaches 0. Each step clears a bit, so it runs at most about log n times. add goes the other way: adding the lowest set bit jumps to the next, bigger block that also contains the position, until it falls off the end of the array. rangeSum(left, right) is prefixSum(right) - prefixSum(left - 1), the same trick as a plain prefix sum array.

Time O(log n) per add, prefix sum or range sumSpace O(n)

class FenwickTree:
    """Prefix sums over an array that changes. Positions are 1-indexed inside:
    tree[i] holds the sum of the (i & -i) values that end at position i."""

    def __init__(self, n: int) -> None:
        # Slot 0 stays unused: 0 & -0 is 0, so the loops would never move.
        self.tree = [0] * (n + 1)

    def add(self, i: int, delta: int) -> None:
        """Add `delta` to nums[i]."""
        i += 1  # Shift to 1-indexed.
        while i < len(self.tree):
            self.tree[i] += delta
            i += i & -i  # Climb to the next block that also covers this position.

    def prefix_sum(self, i: int) -> int:
        """Sum of nums[0..i], inclusive."""
        i += 1
        total = 0
        while i > 0:
            total += self.tree[i]
            i -= i & -i  # Drop the lowest set bit: jump to the block just before this one.
        return total

    def range_sum(self, left: int, right: int) -> int:
        """Sum of nums[left..right], inclusive."""
        return self.prefix_sum(right) - self.prefix_sum(left - 1)


if __name__ == "__main__":
    nums = [3, 2, -1, 6, 5, 4, -3, 3]
    tree = FenwickTree(len(nums))
    for i, num in enumerate(nums):
        tree.add(i, num)
    print(tree.prefix_sum(4))  # 15
    print(tree.range_sum(2, 5))  # 14
    tree.add(3, 4)  # nums[3] goes from 6 to 10.
    print(tree.range_sum(2, 5))  # 18
    print(tree.range_sum(0, 7))  # 23
    print(tree.range_sum(6, 6))  # -3
// Prefix sums over an array that changes. Positions are 1-indexed inside:
// tree[i] holds the sum of the (i & -i) values that end at position i.
export class FenwickTree {
  private tree: number[];

  constructor(n: number) {
    // Slot 0 stays unused: 0 & -0 is 0, so the loops would never move.
    this.tree = new Array<number>(n + 1).fill(0);
  }

  // Add `delta` to nums[i].
  add(i: number, delta: number): void {
    i++; // Shift to 1-indexed.
    while (i < this.tree.length) {
      this.tree[i] += delta;
      i += i & -i; // Climb to the next block that also covers this position.
    }
  }

  // Sum of nums[0..i], inclusive.
  prefixSum(i: number): number {
    i++;
    let total = 0;
    while (i > 0) {
      total += this.tree[i];
      i -= i & -i; // Drop the lowest set bit: jump to the block just before this one.
    }
    return total;
  }

  // Sum of nums[left..right], inclusive.
  rangeSum(left: number, right: number): number {
    return this.prefixSum(right) - this.prefixSum(left - 1);
  }
}

const nums = [3, 2, -1, 6, 5, 4, -3, 3];
const tree = new FenwickTree(nums.length);
nums.forEach((num, i) => tree.add(i, num));
console.log(tree.prefixSum(4)); // 15
console.log(tree.rangeSum(2, 5)); // 14
tree.add(3, 4); // nums[3] goes from 6 to 10.
console.log(tree.rangeSum(2, 5)); // 18
console.log(tree.rangeSum(0, 7)); // 23
console.log(tree.rangeSum(6, 6)); // -3
package main

import "fmt"

// fenwickTree keeps prefix sums over an array that changes. Positions are
// 1-indexed inside: tree[i] holds the sum of the (i & -i) values that end at
// position i.
type fenwickTree struct {
	tree []int
}

func newFenwickTree(n int) *fenwickTree {
	// Slot 0 stays unused: 0 & -0 is 0, so the loops would never move.
	return &fenwickTree{tree: make([]int, n+1)}
}

// add adds delta to nums[i].
func (f *fenwickTree) add(i, delta int) {
	i++ // Shift to 1-indexed.
	for i < len(f.tree) {
		f.tree[i] += delta
		i += i & -i // Climb to the next block that also covers this position.
	}
}

// prefixSum returns the sum of nums[0..i], inclusive.
func (f *fenwickTree) prefixSum(i int) int {
	i++
	total := 0
	for i > 0 {
		total += f.tree[i]
		i -= i & -i // Drop the lowest set bit: jump to the block just before this one.
	}
	return total
}

// rangeSum returns the sum of nums[left..right], inclusive.
func (f *fenwickTree) rangeSum(left, right int) int {
	return f.prefixSum(right) - f.prefixSum(left-1)
}

func main() {
	nums := []int{3, 2, -1, 6, 5, 4, -3, 3}
	tree := newFenwickTree(len(nums))
	for i, num := range nums {
		tree.add(i, num)
	}
	fmt.Println(tree.prefixSum(4))   // 15
	fmt.Println(tree.rangeSum(2, 5)) // 14
	tree.add(3, 4)                   // nums[3] goes from 6 to 10.
	fmt.Println(tree.rangeSum(2, 5)) // 18
	fmt.Println(tree.rangeSum(0, 7)) // 23
	fmt.Println(tree.rangeSum(6, 6)) // -3
}
using System;

public class Solution
{
    // Prefix sums over an array that changes. Positions are 1-indexed inside:
    // tree[i] holds the sum of the (i & -i) values that end at position i.
    public class FenwickTree
    {
        readonly int[] tree;

        public FenwickTree(int n)
        {
            // Slot 0 stays unused: 0 & -0 is 0, so the loops would never move.
            tree = new int[n + 1];
        }

        // Add delta to nums[i].
        public void Add(int i, int delta)
        {
            i++; // Shift to 1-indexed.
            while (i < tree.Length)
            {
                tree[i] += delta;
                i += i & -i; // Climb to the next block that also covers this position.
            }
        }

        // Sum of nums[0..i], inclusive.
        public int PrefixSum(int i)
        {
            i++;
            int total = 0;
            while (i > 0)
            {
                total += tree[i];
                i -= i & -i; // Drop the lowest set bit: jump to the block just before this one.
            }
            return total;
        }

        // Sum of nums[left..right], inclusive.
        public int RangeSum(int left, int right) => PrefixSum(right) - PrefixSum(left - 1);
    }

    public static void Main()
    {
        int[] nums = { 3, 2, -1, 6, 5, 4, -3, 3 };
        var tree = new FenwickTree(nums.Length);
        for (int i = 0; i < nums.Length; i++) tree.Add(i, nums[i]);
        Console.WriteLine(tree.PrefixSum(4)); // 15
        Console.WriteLine(tree.RangeSum(2, 5)); // 14
        tree.Add(3, 4); // nums[3] goes from 6 to 10.
        Console.WriteLine(tree.RangeSum(2, 5)); // 18
        Console.WriteLine(tree.RangeSum(0, 7)); // 23
        Console.WriteLine(tree.RangeSum(6, 6)); // -3
    }
}
public class Solution {
    // Prefix sums over an array that changes. Positions are 1-indexed inside:
    // tree[i] holds the sum of the (i & -i) values that end at position i.
    static class FenwickTree {
        private final int[] tree;

        FenwickTree(int n) {
            // Slot 0 stays unused: 0 & -0 is 0, so the loops would never move.
            tree = new int[n + 1];
        }

        // Add delta to nums[i].
        void add(int i, int delta) {
            i++; // Shift to 1-indexed.
            while (i < tree.length) {
                tree[i] += delta;
                i += i & -i; // Climb to the next block that also covers this position.
            }
        }

        // Sum of nums[0..i], inclusive.
        int prefixSum(int i) {
            i++;
            int total = 0;
            while (i > 0) {
                total += tree[i];
                i -= i & -i; // Drop the lowest set bit: jump to the block just before this one.
            }
            return total;
        }

        // Sum of nums[left..right], inclusive.
        int rangeSum(int left, int right) {
            return prefixSum(right) - prefixSum(left - 1);
        }
    }

    public static void main(String[] args) {
        int[] nums = {3, 2, -1, 6, 5, 4, -3, 3};
        FenwickTree tree = new FenwickTree(nums.length);
        for (int i = 0; i < nums.length; i++) tree.add(i, nums[i]);
        System.out.println(tree.prefixSum(4)); // 15
        System.out.println(tree.rangeSum(2, 5)); // 14
        tree.add(3, 4); // nums[3] goes from 6 to 10.
        System.out.println(tree.rangeSum(2, 5)); // 18
        System.out.println(tree.rangeSum(0, 7)); // 23
        System.out.println(tree.rangeSum(6, 6)); // -3
    }
}

Worked Problems

Each solution stays folded until you open it, so you can try the problem first. Every one is a complete program in Python, TypeScript, Go, C# and Java, run and checked against its expected output.

Range Sum Query - Mutable

MEDIUMLeetCode 307

Problem

Build a class over an integer array nums with two operations. update(index, val) sets nums[index] to val, and sumRange(left, right) returns the sum of nums[left] through nums[right], inclusive. The two kinds of call arrive interleaved, many of each.

Example: for nums = [1, 3, 5], sumRange(0, 2) returns 9. After update(1, 2) the array is [1, 2, 5], and sumRange(0, 2) returns 8.

Show Solution

Approach

This is a segment tree stored in a flat array, numbered like the array layout from Heaps but starting at 1: node k has children 2k and 2k + 1. build fills the leaves and adds each pair on the way back up, which takes O(n).

update walks from the root down to the one leaf that holds index, always picking the half that contains it. It sets the leaf, then recomputes each sum on the way back up. That's one node per level, so O(log n).

sumRange asks each node how its range overlaps the query:

  • No overlap: return 0 without looking further.
  • Fully inside: return the stored sum. This is where the speed comes from, because a whole block is answered in one step.
  • Partial overlap: ask both children and add their answers.

Only nodes that straddle an end of the query get split, and there are at most two of those per level, so a query visits O(log n) nodes. The array has 4n slots because, when n isn't a power of two, node numbers can run past 2n. 4n is always enough.

Time O(n) to build, O(log n) per update or sumRangeSpace O(n)

class NumArray:
    """Segment tree for range sums with point updates. Node 1 covers the whole
    array, and node k's children 2k and 2k + 1 each cover half of its range."""

    def __init__(self, nums: list[int]) -> None:
        self.n = len(nums)
        self.tree = [0] * (4 * self.n)  # 4n slots always fit every node.
        self._build(nums, 1, 0, self.n - 1)

    def update(self, index: int, val: int) -> None:
        """Set nums[index] to val."""
        self._update_node(1, 0, self.n - 1, index, val)

    def sum_range(self, left: int, right: int) -> int:
        """Sum of nums[left..right], inclusive."""
        return self._query_node(1, 0, self.n - 1, left, right)

    def _build(self, nums: list[int], node: int, lo: int, hi: int) -> None:
        if lo == hi:
            self.tree[node] = nums[lo]
            return
        mid = (lo + hi) // 2
        self._build(nums, 2 * node, lo, mid)
        self._build(nums, 2 * node + 1, mid + 1, hi)
        self.tree[node] = self.tree[2 * node] + self.tree[2 * node + 1]

    def _update_node(self, node: int, lo: int, hi: int, index: int, val: int) -> None:
        if lo == hi:
            self.tree[node] = val
            return
        # Only one half holds index. Fix that half, then recompute this node's sum.
        mid = (lo + hi) // 2
        if index <= mid:
            self._update_node(2 * node, lo, mid, index, val)
        else:
            self._update_node(2 * node + 1, mid + 1, hi, index, val)
        self.tree[node] = self.tree[2 * node] + self.tree[2 * node + 1]

    def _query_node(self, node: int, lo: int, hi: int, left: int, right: int) -> int:
        if right < lo or hi < left:
            return 0  # No overlap: this node adds nothing.
        if left <= lo and hi <= right:
            return self.tree[node]  # Fully inside: use the stored sum.
        # Partial overlap: split the question between the two halves.
        mid = (lo + hi) // 2
        return (
            self._query_node(2 * node, lo, mid, left, right)
            + self._query_node(2 * node + 1, mid + 1, hi, left, right)
        )


if __name__ == "__main__":
    arr = NumArray([1, 3, 5])
    print(arr.sum_range(0, 2))  # 9
    arr.update(1, 2)
    print(arr.sum_range(0, 2))  # 8
    arr = NumArray([5, 8, 6, 3, 2, 7, 2, 6])
    print(arr.sum_range(2, 5))  # 18
    arr.update(3, 10)
    print(arr.sum_range(2, 5))  # 25
    print(arr.sum_range(7, 7))  # 6
// Segment tree for range sums with point updates. Node 1 covers the whole
// array, and node k's children 2k and 2k + 1 each cover half of its range.
export class NumArray {
  private n: number;
  private tree: number[];

  constructor(nums: number[]) {
    this.n = nums.length;
    this.tree = new Array<number>(4 * this.n).fill(0); // 4n slots always fit every node.
    this.build(nums, 1, 0, this.n - 1);
  }

  // Set nums[index] to val.
  update(index: number, val: number): void {
    this.updateNode(1, 0, this.n - 1, index, val);
  }

  // Sum of nums[left..right], inclusive.
  sumRange(left: number, right: number): number {
    return this.queryNode(1, 0, this.n - 1, left, right);
  }

  private build(nums: number[], node: number, lo: number, hi: number): void {
    if (lo === hi) {
      this.tree[node] = nums[lo];
      return;
    }
    const mid = Math.floor((lo + hi) / 2);
    this.build(nums, 2 * node, lo, mid);
    this.build(nums, 2 * node + 1, mid + 1, hi);
    this.tree[node] = this.tree[2 * node] + this.tree[2 * node + 1];
  }

  private updateNode(node: number, lo: number, hi: number, index: number, val: number): void {
    if (lo === hi) {
      this.tree[node] = val;
      return;
    }
    // Only one half holds index. Fix that half, then recompute this node's sum.
    const mid = Math.floor((lo + hi) / 2);
    if (index <= mid) this.updateNode(2 * node, lo, mid, index, val);
    else this.updateNode(2 * node + 1, mid + 1, hi, index, val);
    this.tree[node] = this.tree[2 * node] + this.tree[2 * node + 1];
  }

  private queryNode(node: number, lo: number, hi: number, left: number, right: number): number {
    if (right < lo || hi < left) return 0; // No overlap: this node adds nothing.
    if (left <= lo && hi <= right) return this.tree[node]; // Fully inside: use the stored sum.
    // Partial overlap: split the question between the two halves.
    const mid = Math.floor((lo + hi) / 2);
    return (
      this.queryNode(2 * node, lo, mid, left, right) +
      this.queryNode(2 * node + 1, mid + 1, hi, left, right)
    );
  }
}

let arr = new NumArray([1, 3, 5]);
console.log(arr.sumRange(0, 2)); // 9
arr.update(1, 2);
console.log(arr.sumRange(0, 2)); // 8
arr = new NumArray([5, 8, 6, 3, 2, 7, 2, 6]);
console.log(arr.sumRange(2, 5)); // 18
arr.update(3, 10);
console.log(arr.sumRange(2, 5)); // 25
console.log(arr.sumRange(7, 7)); // 6
package main

import "fmt"

// numArray is a segment tree for range sums with point updates. Node 1
// covers the whole array, and node k's children 2k and 2k + 1 each cover
// half of its range.
type numArray struct {
	n    int
	tree []int
}

func newNumArray(nums []int) *numArray {
	a := &numArray{n: len(nums), tree: make([]int, 4*len(nums))} // 4n slots always fit every node.
	a.build(nums, 1, 0, a.n-1)
	return a
}

// update sets nums[index] to val.
func (a *numArray) update(index, val int) {
	a.updateNode(1, 0, a.n-1, index, val)
}

// sumRange returns the sum of nums[left..right], inclusive.
func (a *numArray) sumRange(left, right int) int {
	return a.queryNode(1, 0, a.n-1, left, right)
}

func (a *numArray) build(nums []int, node, lo, hi int) {
	if lo == hi {
		a.tree[node] = nums[lo]
		return
	}
	mid := (lo + hi) / 2
	a.build(nums, 2*node, lo, mid)
	a.build(nums, 2*node+1, mid+1, hi)
	a.tree[node] = a.tree[2*node] + a.tree[2*node+1]
}

func (a *numArray) updateNode(node, lo, hi, index, val int) {
	if lo == hi {
		a.tree[node] = val
		return
	}
	// Only one half holds index. Fix that half, then recompute this node's sum.
	mid := (lo + hi) / 2
	if index <= mid {
		a.updateNode(2*node, lo, mid, index, val)
	} else {
		a.updateNode(2*node+1, mid+1, hi, index, val)
	}
	a.tree[node] = a.tree[2*node] + a.tree[2*node+1]
}

func (a *numArray) queryNode(node, lo, hi, left, right int) int {
	if right < lo || hi < left {
		return 0 // No overlap: this node adds nothing.
	}
	if left <= lo && hi <= right {
		return a.tree[node] // Fully inside: use the stored sum.
	}
	// Partial overlap: split the question between the two halves.
	mid := (lo + hi) / 2
	return a.queryNode(2*node, lo, mid, left, right) +
		a.queryNode(2*node+1, mid+1, hi, left, right)
}

func main() {
	arr := newNumArray([]int{1, 3, 5})
	fmt.Println(arr.sumRange(0, 2)) // 9
	arr.update(1, 2)
	fmt.Println(arr.sumRange(0, 2)) // 8
	arr = newNumArray([]int{5, 8, 6, 3, 2, 7, 2, 6})
	fmt.Println(arr.sumRange(2, 5)) // 18
	arr.update(3, 10)
	fmt.Println(arr.sumRange(2, 5)) // 25
	fmt.Println(arr.sumRange(7, 7)) // 6
}
using System;

public class Solution
{
    // Segment tree for range sums with point updates. Node 1 covers the whole
    // array, and node k's children 2k and 2k + 1 each cover half of its range.
    public class NumArray
    {
        readonly int n;
        readonly int[] tree;

        public NumArray(int[] nums)
        {
            n = nums.Length;
            tree = new int[4 * n]; // 4n slots always fit every node.
            Build(nums, 1, 0, n - 1);
        }

        // Set nums[index] to val.
        public void Update(int index, int val) => UpdateNode(1, 0, n - 1, index, val);

        // Sum of nums[left..right], inclusive.
        public int SumRange(int left, int right) => QueryNode(1, 0, n - 1, left, right);

        void Build(int[] nums, int node, int lo, int hi)
        {
            if (lo == hi)
            {
                tree[node] = nums[lo];
                return;
            }
            int mid = (lo + hi) / 2;
            Build(nums, 2 * node, lo, mid);
            Build(nums, 2 * node + 1, mid + 1, hi);
            tree[node] = tree[2 * node] + tree[2 * node + 1];
        }

        void UpdateNode(int node, int lo, int hi, int index, int val)
        {
            if (lo == hi)
            {
                tree[node] = val;
                return;
            }
            // Only one half holds index. Fix that half, then recompute this node's sum.
            int mid = (lo + hi) / 2;
            if (index <= mid) UpdateNode(2 * node, lo, mid, index, val);
            else UpdateNode(2 * node + 1, mid + 1, hi, index, val);
            tree[node] = tree[2 * node] + tree[2 * node + 1];
        }

        int QueryNode(int node, int lo, int hi, int left, int right)
        {
            if (right < lo || hi < left) return 0; // No overlap: this node adds nothing.
            if (left <= lo && hi <= right) return tree[node]; // Fully inside: use the stored sum.
            // Partial overlap: split the question between the two halves.
            int mid = (lo + hi) / 2;
            return QueryNode(2 * node, lo, mid, left, right)
                + QueryNode(2 * node + 1, mid + 1, hi, left, right);
        }
    }

    public static void Main()
    {
        var arr = new NumArray(new[] { 1, 3, 5 });
        Console.WriteLine(arr.SumRange(0, 2)); // 9
        arr.Update(1, 2);
        Console.WriteLine(arr.SumRange(0, 2)); // 8
        arr = new NumArray(new[] { 5, 8, 6, 3, 2, 7, 2, 6 });
        Console.WriteLine(arr.SumRange(2, 5)); // 18
        arr.Update(3, 10);
        Console.WriteLine(arr.SumRange(2, 5)); // 25
        Console.WriteLine(arr.SumRange(7, 7)); // 6
    }
}
public class Solution {
    // Segment tree for range sums with point updates. Node 1 covers the whole
    // array, and node k's children 2k and 2k + 1 each cover half of its range.
    static class NumArray {
        private final int n;
        private final int[] tree;

        NumArray(int[] nums) {
            n = nums.length;
            tree = new int[4 * n]; // 4n slots always fit every node.
            build(nums, 1, 0, n - 1);
        }

        // Set nums[index] to val.
        void update(int index, int val) {
            updateNode(1, 0, n - 1, index, val);
        }

        // Sum of nums[left..right], inclusive.
        int sumRange(int left, int right) {
            return queryNode(1, 0, n - 1, left, right);
        }

        private void build(int[] nums, int node, int lo, int hi) {
            if (lo == hi) {
                tree[node] = nums[lo];
                return;
            }
            int mid = (lo + hi) / 2;
            build(nums, 2 * node, lo, mid);
            build(nums, 2 * node + 1, mid + 1, hi);
            tree[node] = tree[2 * node] + tree[2 * node + 1];
        }

        private void updateNode(int node, int lo, int hi, int index, int val) {
            if (lo == hi) {
                tree[node] = val;
                return;
            }
            // Only one half holds index. Fix that half, then recompute this node's sum.
            int mid = (lo + hi) / 2;
            if (index <= mid) updateNode(2 * node, lo, mid, index, val);
            else updateNode(2 * node + 1, mid + 1, hi, index, val);
            tree[node] = tree[2 * node] + tree[2 * node + 1];
        }

        private int queryNode(int node, int lo, int hi, int left, int right) {
            if (right < lo || hi < left) return 0; // No overlap: this node adds nothing.
            if (left <= lo && hi <= right) return tree[node]; // Fully inside: use the stored sum.
            // Partial overlap: split the question between the two halves.
            int mid = (lo + hi) / 2;
            return queryNode(2 * node, lo, mid, left, right)
                    + queryNode(2 * node + 1, mid + 1, hi, left, right);
        }
    }

    public static void main(String[] args) {
        NumArray arr = new NumArray(new int[] {1, 3, 5});
        System.out.println(arr.sumRange(0, 2)); // 9
        arr.update(1, 2);
        System.out.println(arr.sumRange(0, 2)); // 8
        arr = new NumArray(new int[] {5, 8, 6, 3, 2, 7, 2, 6});
        System.out.println(arr.sumRange(2, 5)); // 18
        arr.update(3, 10);
        System.out.println(arr.sumRange(2, 5)); // 25
        System.out.println(arr.sumRange(7, 7)); // 6
    }
}

Count of Smaller Numbers After Self

HARDLeetCode 315

Problem

Given an integer array nums, return an array counts where counts[i] is the number of elements to the right of index i that are strictly smaller than nums[i].

Example: [5, 2, 6, 1] returns [2, 1, 1, 0]. Two values right of 5 are smaller (2 and 1), one right of 2 (1), one right of 6 (1), and nothing sits right of the final 1.

Show Solution

Approach

Checking every pair is O(n²). Instead, walk from right to left and keep a tally of the values you've passed. When you reach nums[i], the tally holds exactly the values to its right, and the answer is how many of them are smaller: a prefix sum over the tally. Then add nums[i] to the tally and move on. A Fenwick tree indexed by value does both steps in O(log n).

The catch is that values can be negative (and in similar problems as large as 10^9), so they can't index an array directly. Coordinate compression fixes that: sort the distinct values and replace each value with its rank. Ranks run from 0 to at most n - 1 and keep the order of the values, which is all the query cares about.

ranks: 1 -> 0, 2 -> 1, 5 -> 2, 6 -> 3

i  nums[i]  rank  prefixSum(rank - 1)  seen afterward
3  1        0     0                    [1, 0, 0, 0]
2  6        3     1                    [1, 0, 0, 1]
1  2        1     1                    [1, 1, 0, 1]
0  5        2     2                    [1, 1, 1, 1]
Walking [5, 2, 6, 1] from the right. The count for each value is the number of smaller ranks already seen.

Counting Smaller Values With a Fenwick Tree

Step 1 of 15

nums

seen.tree

  • Value being placed, block being added to
  • Block being read
  • Already in the tree, or counted

Ranks stand in for the values: 1, 2, 5, 6 become 0, 1, 2, 3, at tree positions 1..4. Walk from the right, so seen only holds the values after i.

All 15 steps as text
  1. Ranks stand in for the values: 1, 2, 5, 6 become 0, 1, 2, 3, at tree positions 1..4. Walk from the right, so seen only holds the values after i.
  2. nums[3] = 1 has rank 0. No rank is below 0, so prefixSum(-1) reads nothing and counts[3] = 0.
  3. Add rank 0 to seen, at position 1. tree[1] covers position 1 alone, so it goes up to 1.
  4. 1 + (1 & -1) = 2 is the next block that also covers position 1. tree[2] covers positions 1..2, so it goes up to 1.
  5. 2 + (2 & -2) = 4 is the next block that also covers position 1. tree[4] covers positions 1..4, so it goes up to 1. The next jump, to 8, is past the end.
  6. nums[2] = 6 has rank 3. prefixSum(2) counts the smaller ranks from position 3 down. tree[3] covers position 3 alone and holds 0.
  7. 3 - (3 & -3) = 2, the block just before. tree[2] covers positions 1..2 and holds 1, so the total is 0 + 1 = 1. Clearing its lowest bit leaves 0, so counts[2] = 1.
  8. Add rank 3 to seen, at position 4. tree[4] covers positions 1..4, so it goes up to 2. The next jump, to 8, is past the end.
  9. nums[1] = 2 has rank 1. prefixSum(0) counts the smaller ranks from position 1 down. tree[1] covers position 1 alone and holds 1. Clearing its lowest bit leaves 0, so counts[1] = 1.
  10. Add rank 1 to seen, at position 2. tree[2] covers positions 1..2, so it goes up to 2.
  11. 2 + (2 & -2) = 4 is the next block that also covers position 2. tree[4] covers positions 1..4, so it goes up to 3. The next jump, to 8, is past the end.
  12. nums[0] = 5 has rank 2. prefixSum(1) counts the smaller ranks from position 2 down. tree[2] covers positions 1..2 and holds 2. Clearing its lowest bit leaves 0, so counts[0] = 2.
  13. Add rank 2 to seen, at position 3. tree[3] covers position 3 alone, so it goes up to 1.
  14. 3 + (3 & -3) = 4 is the next block that also covers position 3. tree[4] covers positions 1..4, so it goes up to 4. The next jump, to 8, is past the end.
  15. counts = [2, 1, 1, 0]. Each value cost one prefix sum and one add, each touching at most about log n blocks, instead of rescanning everything to its right.

The query stops at rank - 1 because the problem wants strictly smaller values. Equal values share a rank, so they're left out.

Time O(n log n)Space O(n)

def count_smaller(nums: list[int]) -> list[int]:
    """For each nums[i], how many values to its right are strictly smaller."""
    # Coordinate compression: replace each value with its rank among the
    # distinct values, so the tree needs one slot per distinct value.
    distinct = sorted(set(nums))
    rank = {value: r for r, value in enumerate(distinct)}

    seen = FenwickTree(len(distinct))  # How many of each rank we've passed.
    counts = [0] * len(nums)
    # Walk right to left, so the tree holds exactly the values after i.
    for i in range(len(nums) - 1, -1, -1):
        r = rank[nums[i]]
        counts[i] = seen.prefix_sum(r - 1)  # Ranks below r are the strictly smaller values.
        seen.add(r, 1)
    return counts


class FenwickTree:
    """The Fenwick tree from the template: tree[i] (1-indexed) holds the sum
    of the (i & -i) values that end at position i."""

    def __init__(self, n: int) -> None:
        self.tree = [0] * (n + 1)

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

    def prefix_sum(self, i: int) -> int:
        """Sum of positions 0..i, inclusive. prefix_sum(-1) is 0."""
        i += 1
        total = 0
        while i > 0:
            total += self.tree[i]
            i -= i & -i
        return total


if __name__ == "__main__":
    print(count_smaller([5, 2, 6, 1]))  # [2, 1, 1, 0]
    print(count_smaller([-1, -1]))  # [0, 0]
    print(count_smaller([100, -50, 7, 100, -50]))  # [3, 0, 1, 1, 0]
// For each nums[i], how many values to its right are strictly smaller.
export function countSmaller(nums: number[]): number[] {
  // Coordinate compression: replace each value with its rank among the
  // distinct values, so the tree needs one slot per distinct value.
  const sorted = [...new Set(nums)].sort((a, b) => a - b);
  const rank = new Map<number, number>();
  sorted.forEach((value, r) => rank.set(value, r));

  const seen = new FenwickTree(sorted.length); // How many of each rank we've passed.
  const counts = new Array<number>(nums.length).fill(0);
  // Walk right to left, so the tree holds exactly the values after i.
  for (let i = nums.length - 1; i >= 0; i--) {
    const r = rank.get(nums[i])!;
    counts[i] = seen.prefixSum(r - 1); // Ranks below r are the strictly smaller values.
    seen.add(r, 1);
  }
  return counts;
}

// The Fenwick tree from the template: tree[i] (1-indexed) holds the sum of
// the (i & -i) values that end at position i.
class FenwickTree {
  private tree: number[];

  constructor(n: number) {
    this.tree = new Array<number>(n + 1).fill(0);
  }

  add(i: number, delta: number): void {
    for (i++; i < this.tree.length; i += i & -i) this.tree[i] += delta;
  }

  // Sum of positions 0..i, inclusive. prefixSum(-1) is 0.
  prefixSum(i: number): number {
    let total = 0;
    for (i++; i > 0; i -= i & -i) total += this.tree[i];
    return total;
  }
}

console.log(JSON.stringify(countSmaller([5, 2, 6, 1]))); // [2,1,1,0]
console.log(JSON.stringify(countSmaller([-1, -1]))); // [0,0]
console.log(JSON.stringify(countSmaller([100, -50, 7, 100, -50]))); // [3,0,1,1,0]
package main

import (
	"fmt"
	"slices"
)

// countSmaller returns, for each nums[i], how many values to its right are
// strictly smaller.
func countSmaller(nums []int) []int {
	// Coordinate compression: replace each value with its rank among the
	// distinct values, so the tree needs one slot per distinct value.
	distinct := slices.Clone(nums)
	slices.Sort(distinct)
	distinct = slices.Compact(distinct)
	rank := make(map[int]int, len(distinct))
	for r, value := range distinct {
		rank[value] = r
	}

	seen := newFenwickTree(len(distinct)) // How many of each rank we've passed.
	counts := make([]int, len(nums))
	// Walk right to left, so the tree holds exactly the values after i.
	for i := len(nums) - 1; i >= 0; i-- {
		r := rank[nums[i]]
		counts[i] = seen.prefixSum(r - 1) // Ranks below r are the strictly smaller values.
		seen.add(r, 1)
	}
	return counts
}

// fenwickTree is the Fenwick tree from the template: tree[i] (1-indexed)
// holds the sum of the (i & -i) values that end at position i.
type fenwickTree struct {
	tree []int
}

func newFenwickTree(n int) *fenwickTree {
	return &fenwickTree{tree: make([]int, n+1)}
}

func (f *fenwickTree) add(i, delta int) {
	for i++; i < len(f.tree); i += i & -i {
		f.tree[i] += delta
	}
}

// prefixSum returns the sum of positions 0..i, inclusive. prefixSum(-1) is 0.
func (f *fenwickTree) prefixSum(i int) int {
	total := 0
	for i++; i > 0; i -= i & -i {
		total += f.tree[i]
	}
	return total
}

func main() {
	fmt.Println(countSmaller([]int{5, 2, 6, 1}))            // [2 1 1 0]
	fmt.Println(countSmaller([]int{-1, -1}))                // [0 0]
	fmt.Println(countSmaller([]int{100, -50, 7, 100, -50})) // [3 0 1 1 0]
}
using System;
using System.Collections.Generic;
using System.Linq;

public class Solution
{
    // For each nums[i], how many values to its right are strictly smaller.
    public static int[] CountSmaller(int[] nums)
    {
        // Coordinate compression: replace each value with its rank among the
        // distinct values, so the tree needs one slot per distinct value.
        int[] distinct = nums.Distinct().OrderBy(v => v).ToArray();
        var rank = new Dictionary<int, int>();
        for (int r = 0; r < distinct.Length; r++) rank[distinct[r]] = r;

        var seen = new FenwickTree(distinct.Length); // How many of each rank we've passed.
        var counts = new int[nums.Length];
        // Walk right to left, so the tree holds exactly the values after i.
        for (int i = nums.Length - 1; i >= 0; i--)
        {
            int r = rank[nums[i]];
            counts[i] = seen.PrefixSum(r - 1); // Ranks below r are the strictly smaller values.
            seen.Add(r, 1);
        }
        return counts;
    }

    // The Fenwick tree from the template: tree[i] (1-indexed) holds the sum of
    // the (i & -i) values that end at position i.
    public class FenwickTree
    {
        readonly int[] tree;

        public FenwickTree(int n)
        {
            tree = new int[n + 1];
        }

        public void Add(int i, int delta)
        {
            for (i++; i < tree.Length; i += i & -i) tree[i] += delta;
        }

        // Sum of positions 0..i, inclusive. PrefixSum(-1) is 0.
        public int PrefixSum(int i)
        {
            int total = 0;
            for (i++; i > 0; i -= i & -i) total += tree[i];
            return total;
        }
    }

    public static void Main()
    {
        Console.WriteLine(Format(CountSmaller(new[] { 5, 2, 6, 1 }))); // [2, 1, 1, 0]
        Console.WriteLine(Format(CountSmaller(new[] { -1, -1 }))); // [0, 0]
        Console.WriteLine(Format(CountSmaller(new[] { 100, -50, 7, 100, -50 }))); // [3, 0, 1, 1, 0]
    }

    static string Format(int[] values) => "[" + string.Join(", ", values) + "]";
}
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class Solution {
    // For each nums[i], how many values to its right are strictly smaller.
    static int[] countSmaller(int[] nums) {
        // Coordinate compression: replace each value with its rank among the
        // distinct values, so the tree needs one slot per distinct value.
        int[] distinct = Arrays.stream(nums).distinct().sorted().toArray();
        Map<Integer, Integer> rank = new HashMap<>();
        for (int r = 0; r < distinct.length; r++) rank.put(distinct[r], r);

        FenwickTree seen = new FenwickTree(distinct.length); // How many of each rank we've passed.
        int[] counts = new int[nums.length];
        // Walk right to left, so the tree holds exactly the values after i.
        for (int i = nums.length - 1; i >= 0; i--) {
            int r = rank.get(nums[i]);
            counts[i] = seen.prefixSum(r - 1); // Ranks below r are the strictly smaller values.
            seen.add(r, 1);
        }
        return counts;
    }

    // The Fenwick tree from the template: tree[i] (1-indexed) holds the sum of
    // the (i & -i) values that end at position i.
    static class FenwickTree {
        private final int[] tree;

        FenwickTree(int n) {
            tree = new int[n + 1];
        }

        void add(int i, int delta) {
            for (i++; i < tree.length; i += i & -i) tree[i] += delta;
        }

        // Sum of positions 0..i, inclusive. prefixSum(-1) is 0.
        int prefixSum(int i) {
            int total = 0;
            for (i++; i > 0; i -= i & -i) total += tree[i];
            return total;
        }
    }

    public static void main(String[] args) {
        System.out.println(Arrays.toString(countSmaller(new int[] {5, 2, 6, 1}))); // [2, 1, 1, 0]
        System.out.println(Arrays.toString(countSmaller(new int[] {-1, -1}))); // [0, 0]
        System.out.println(Arrays.toString(countSmaller(new int[] {100, -50, 7, 100, -50}))); // [3, 0, 1, 1, 0]
    }
}

Common Pitfalls

  • Using index 0 in a Fenwick tree. 0 & -0 is 0, so the update loop never advances. Shift indices to start at 1.
  • Treating LeetCode 307's update as an add. It sets the value, so with a Fenwick tree you add val - nums[index] and keep your copy of the array current.
  • Sizing a recursive segment tree at 2n. When n isn't a power of two, node numbers can run past 2n; 4n is always enough.
  • Counting <= instead of < when values repeat. For strictly smaller values, query up to rank - 1, not rank.
  • Using a Fenwick tree for range minimum or maximum. Those can't be undone by subtraction, so a range can't be split into two prefixes. Use a segment tree.

Practice

More problems to try on your own.