Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Data Structures & Algorithms (DSA)  Module 19 – Range queries and tree queries

Fenwick trees (binary indexed trees)

How a Fenwick tree keeps the prefix sums of a changing array in one list, with updates and queries in O(log n) driven by i & -i, traced on theatre bookings.

  • Advanced
  • 30 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free core, Pro depth: 10 of 30 minutes free
Free: Why plain prefix sums are not enough · What each position covers · Climb to update, descend to sum · 2 more sections. With a pass: 3 in-depth parts and 3 interview answers, about 20 minutes.

What you will learn

  • Implement prefix sums with point updates in O(log n) with the two Fenwick loops
  • Apply range updates with point queries, and range sums with two trees
  • Count inversions and ranks with a Fenwick tree of counts

Before you start

A Fenwick tree, also called a binary indexed tree, keeps the prefix sums of an array whose values keep changing. It is a single list of n + 1 numbers: position i, counted from 1, holds the total of the last i & -i values up to i, where i & -i is the lowest set bit of i. Adding to one value climbs through the positions whose ranges contain it, with i += i & -i. The sum of the first i values descends through ranges that split 1 to i without overlap, with i -= i & -i. Each loop runs at most about log₂ n times, so both operations cost O(log n), where plain prefix sums pay O(n) for every change. A range total is the difference of two prefix sums. Peter Fenwick described the structure in 1994 for the frequency tables of data compression, and it remains the shortest code for sums that keep changing.

Why plain prefix sums are not enough

A theatre’s booking system keeps the number of booked seats in each row and must answer questions such as “how many seats are booked in rows 4 to 12?” while bookings and cancellations keep arriving. The two obvious designs each make one operation slow:

Design Change one row Total of a range of rows
A plain list of rows O(1) O(n): add up the range
Prefix sums O(n): every later sum moves O(1): one subtraction
Fenwick tree O(log n) O(log n): two prefix sums

For 200,000 changes and 200,000 totals over 200,000 rows, either of the first two designs needs about 4 × 10¹⁰ steps in the worst case and the Fenwick tree about 10⁷: the difference between hours and well under a second. A sparse table cannot help either, because it needs data that never changes.

What each position covers

The lowest set bit of i decides how far back position i reaches. In two’s complement, -i flips every bit of i above its lowest set bit, so i & -i keeps only that bit: 12 is 1100 in binary, so 12 & -12 is 4 and position 12 holds the total of rows 9 to 12. Odd positions hold a single row, and a power of two such as 8 holds every row up to itself.

Bars for positions 1 to 16 of a Fenwick tree, each covering the last i & -i rows up to i; tree[11], tree[10] and tree[8] are highlighted.What tree[i] covers: the last i & -i values up to ii1234567891011121314151611357911131522610144412881616Highlighted: prefix(11) reads tree[11], tree[10] and tree[8],whose ranges 11, 9-10 and 1-8 split rows 1 to 11 exactly.

Each position stores the total of a range that ends at it, as long as its lowest set bit

Text description of the diagram

The numbers 1 to 16 across the top are the positions i of the tree, which are also the rows of the theatre. Each position is drawn as a bar that ends under its own number and is as long as the lowest set bit of i, the value of i & -i. The bars sit on five lines, by length, and the number at the left of each line is that length.

  1. Length 1: the odd positions 1, 3, 5, 7, 9, 11, 13 and 15 each cover only their own row.
  2. Length 2: positions 2, 6, 10 and 14 cover two rows each, for example 10 covers rows 9 and 10.
  3. Length 4: positions 4 and 12 cover rows 1 to 4 and rows 9 to 12.
  4. Length 8: position 8 covers rows 1 to 8.
  5. Length 16: position 16 covers all sixteen rows.

Three bars are highlighted: 11, 10 and 8. They are the positions that prefix(11) reads, and their ranges, row 11, rows 9 to 10 and rows 1 to 8, fit together without a gap or an overlap to make rows 1 to 11. An update to row 5 changes every bar that contains row 5: positions 5, 6, 8 and 16.

Two facts make the loops work. Removing the lowest set bit of i lands on the position just before the range of i, so prefix(i) collects ranges that tile 1 to i with no gap and no overlap. Adding the lowest set bit lands on the next position whose range contains i, so add(i, delta) reaches every total that includes row i.

Climb to update, descend to sum

The program books seats in a 16-row theatre, prints the positions each change updates and each total reads, and then checks 1,000 random changes and totals against a plain list:

Bookings in a 16-row theatre, with the positions each step uses

Python · trace/fenwick.py

"""Booked seats per row of a 16-row theatre (synthetic), kept in a Fenwick tree, with the positions each step uses."""

import random


class Fenwick:
    """Prefix sums of values[1..n] while single values change. tree[0] is never used."""

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

    def add(self, i, delta):
        """values[i] += delta: climb to every position whose range contains i."""
        while i <= self.n:
            self.tree[i] += delta
            i += i & -i

    def prefix(self, i):
        """values[1] + ... + values[i]: descend through ranges that split 1..i without overlap."""
        total = 0
        while i > 0:
            total += self.tree[i]
            i -= i & -i
        return total

    def range_sum(self, left, right):
        """values[left] + ... + values[right]."""
        return self.prefix(right) - self.prefix(left - 1)


def climb(i, n):
    path = []
    while i <= n:
        path.append(i)
        i += i & -i
    return path


def descend(i):
    path = []
    while i > 0:
        path.append(i)
        i -= i & -i
    return path


rows = 16
seats = Fenwick(rows)
for row, change in [(5, 4), (12, 2), (3, 3), (11, 1), (5, -1)]:
    seats.add(row, change)
    print(f"row {row:>2} {change:+d} seats: updates positions {climb(row, rows)}")
print("tree[1..16]:", seats.tree[1:])
print(f"rows 1-11: reads positions {descend(11)}, total {seats.prefix(11)}")
print(f"rows 4-12: prefix(12) reads {descend(12)}, prefix(3) reads {descend(3)}, total {seats.range_sum(4, 12)}")

# The same bookings on a plain list, compared on 1,000 random changes and questions.
rng = random.Random(3)
plain = [0] * (rows + 1)
check = Fenwick(rows)
agree = 0
for _ in range(1000):
    row = rng.randint(1, rows)
    change = rng.randint(-2, 5)
    plain[row] += change
    check.add(row, change)
    left = rng.randint(1, rows)
    right = rng.randint(left, rows)
    agree += check.range_sum(left, right) == sum(plain[left : right + 1])
print(f"{agree:,} of 1,000 random range totals agree with a plain list")

Output

row  5 +4 seats: updates positions [5, 6, 8, 16]
row 12 +2 seats: updates positions [12, 16]
row  3 +3 seats: updates positions [3, 4, 8, 16]
row 11 +1 seats: updates positions [11, 12, 16]
row  5 -1 seats: updates positions [5, 6, 8, 16]
tree[1..16]: [0, 0, 3, 3, 3, 3, 0, 6, 0, 0, 1, 3, 0, 0, 0, 9]
rows 1-11: reads positions [11, 10, 8], total 7
rows 4-12: prefix(12) reads [12, 8], prefix(3) reads [3, 2], total 6
1,000 of 1,000 random range totals agree with a plain list

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 fenwick.py

JavaScript · trace/fenwick.mjs

// Booked seats per row of a 16-row theatre (synthetic), kept in a Fenwick tree, with the positions each step uses.

/** Prefix sums of values[1..n] while single values change. tree[0] is never used. */
class Fenwick {
  constructor(n) {
    this.n = n;
    this.tree = new Array(n + 1).fill(0);
  }

  /** values[i] += delta: climb to every position whose range contains i. */
  add(i, delta) {
    for (; i <= this.n; i += i & -i) this.tree[i] += delta;
  }

  /** values[1] + ... + values[i]: descend through ranges that split 1..i without overlap. */
  prefix(i) {
    let total = 0;
    for (; i > 0; i -= i & -i) total += this.tree[i];
    return total;
  }

  /** values[left] + ... + values[right]. */
  rangeSum(left, right) {
    return this.prefix(right) - this.prefix(left - 1);
  }
}

const climb = (i, n) => {
  const path = [];
  for (; i <= n; i += i & -i) path.push(i);
  return path;
};
const descend = (i) => {
  const path = [];
  for (; i > 0; i -= i & -i) path.push(i);
  return path;
};
const list = (a) => `[${a.join(', ')}]`;
/** Digits in groups of three, as Python prints them with a comma format. */
const grouped = (v) => String(v).replace(/\B(?=(\d{3})+(?!\d))/g, ',');

const rows = 16;
const seats = new Fenwick(rows);
for (const [row, change] of [[5, 4], [12, 2], [3, 3], [11, 1], [5, -1]]) {
  seats.add(row, change);
  const sign = change >= 0 ? `+${change}` : `${change}`;
  console.log(`row ${String(row).padStart(2)} ${sign} seats: updates positions ${list(climb(row, rows))}`);
}
console.log(`tree[1..16]: ${list(seats.tree.slice(1))}`);
console.log(`rows 1-11: reads positions ${list(descend(11))}, total ${seats.prefix(11)}`);
console.log(`rows 4-12: prefix(12) reads ${list(descend(12))}, prefix(3) reads ${list(descend(3))}, total ${seats.rangeSum(4, 12)}`);

// The same bookings on a plain array, compared on 1,000 random changes and questions (a seeded generator).
let seed = 3;
const pick = (lo, hi) => {
  seed = (seed * 48271) % 2147483647;
  return lo + (seed % (hi - lo + 1));
};
const plain = new Array(rows + 1).fill(0);
const check = new Fenwick(rows);
let agree = 0;
for (let t = 0; t < 1000; t++) {
  const row = pick(1, rows);
  const change = pick(-2, 5);
  plain[row] += change;
  check.add(row, change);
  const left = pick(1, rows);
  const right = pick(left, rows);
  let total = 0;
  for (let r = left; r <= right; r++) total += plain[r];
  if (check.rangeSum(left, right) === total) agree++;
}
console.log(`${grouped(agree)} of 1,000 random range totals agree with a plain list`);

Output

row  5 +4 seats: updates positions [5, 6, 8, 16]
row 12 +2 seats: updates positions [12, 16]
row  3 +3 seats: updates positions [3, 4, 8, 16]
row 11 +1 seats: updates positions [11, 12, 16]
row  5 -1 seats: updates positions [5, 6, 8, 16]
tree[1..16]: [0, 0, 3, 3, 3, 3, 0, 6, 0, 0, 1, 3, 0, 0, 0, 9]
rows 1-11: reads positions [11, 10, 8], total 7
rows 4-12: prefix(12) reads [12, 8], prefix(3) reads [3, 2], total 6
1,000 of 1,000 random range totals agree with a plain list

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node fenwick.mjs

Booking 4 seats in row 5 updates positions 5, 6, 8 and 16, the four bars of the diagram that contain row 5. The total of rows 1 to 11 reads positions 11, 10 and 8, three numbers that add up to 7. The total of rows 4 to 12 is prefix(12) - prefix(3): two reads for each prefix, and the answer is 6. Position 0 is never used, because 0 & -0 is 0 and a loop from it would never move.

A Fenwick tree over n positions
Operation Time Extra space
add(i, delta): change one value O(log n) O(1)
prefix(i): the sum of the first i values O(log n) O(1)
The total of positions l to r O(log n) O(1)
Build with n separate adds O(n log n) O(n)

What else the same two loops do

Several techniques reuse the two loops without changing them. Building in O(n) passes each position’s finished total to the one position above it, instead of making n separate adds. A tree over the differences of the array adds a value to a whole range of rows with two updates and reads single rows. Two trees, one over the differences and one over the differences times their positions, give range sums after range updates. Counting inversions scans the values once and asks a tree of counts how many larger values came before each one. Coordinate compression replaces large or scattered values by their ranks from 1 to n, so a tree of counts stays small. Walking down the bits finds the k-th smallest value in a tree of counts in O(log n), which gives running medians and percentiles.

Exercise and self-check

Process bookings, cancellations and range totals in a hall of n rows. The sample tests include totals above 2³¹, which a 32-bit integer cannot hold.

Exercise · Medium · Python, JavaScript

Bookings that change, and totals over ranges of rows

A hall has n rows, numbered from 1 to n, and every row starts with 0 bookings. Write range_totals(n, operations) (JavaScript: rangeTotals) that processes the operations in order:

  • ("add", row, delta) (JavaScript: ["add", row, delta]) changes the bookings of one row by delta, which may be negative when bookings are cancelled;
  • ("sum", left, right) asks for the total bookings of rows left to right, both included.

Return the answers of the "sum" operations, in order. The sample tests replay the theatre of the lesson, check rows at both ends, cancellations and repeated changes, compare your answers with a plain list on random cases, and include totals larger than 2³¹.

Python · Starter code · totals.py

def range_totals(n, operations):
    """Answers of the ("sum", left, right) operations, in order; ("add", row, delta) changes one row."""
    raise NotImplementedError("write range_totals")
The sample tests · test_totals.py
import random

from totals import range_totals

THEATRE = [("add", 5, 4), ("add", 12, 2), ("add", 3, 3), ("add", 11, 1), ("add", 5, -1), ("sum", 1, 11), ("sum", 4, 12)]


def slow_totals(n, operations):
    rows = [0] * (n + 1)
    out = []
    for op, a, b in operations:
        if op == "add":
            rows[a] += b
        else:
            out.append(sum(rows[a : b + 1]))
    return out


def test_theatre():
    """the theatre of the lesson"""
    assert range_totals(16, THEATRE) == [7, 6]


def test_both_ends_and_single_rows():
    """the first row, the last row and ranges of one row"""
    ops = [("add", 1, 2), ("add", 9, 7), ("sum", 1, 1), ("sum", 9, 9), ("sum", 1, 9), ("sum", 2, 8)]
    assert range_totals(9, ops) == [2, 7, 9, 0]


def test_cancellations_and_repeats():
    """negative changes and the same row changed many times"""
    ops = [("add", 4, 10), ("add", 4, -3), ("add", 4, -7), ("sum", 1, 6), ("add", 4, 1), ("sum", 4, 4)]
    assert range_totals(6, ops) == [0, 1]


def test_random_against_a_plain_list():
    """300 random cases, compared with a plain list"""
    rng = random.Random(9)
    for _ in range(300):
        n = rng.randint(1, 30)
        ops = []
        for _ in range(rng.randint(1, 40)):
            if rng.random() < 0.5:
                ops.append(("add", rng.randint(1, n), rng.randint(-5, 9)))
            else:
                left = rng.randint(1, n)
                ops.append(("sum", left, rng.randint(left, n)))
        assert range_totals(n, ops) == slow_totals(n, ops), (n, ops)


def test_totals_above_two_to_the_31():
    """1,000 rows of up to 10**9 bookings: totals far above 2**31"""
    rng = random.Random(4)
    n = 1000
    ops = [("add", row, rng.randint(10**8, 10**9)) for row in range(1, n + 1)]
    ops += [("sum", 1, n), ("sum", 1, n // 2), ("sum", n // 2 + 1, n)]
    answers = range_totals(n, ops)
    assert answers == slow_totals(n, ops)
    assert answers[0] > 2**31

JavaScript · Starter code · totals.mjs

/** Answers of the ["sum", left, right] operations, in order; ["add", row, delta] changes one row. */
export function rangeTotals(n, operations) {
  throw new Error('write rangeTotals');
}
The sample tests · totals.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { rangeTotals } from './totals.mjs';

const THEATRE = [['add', 5, 4], ['add', 12, 2], ['add', 3, 3], ['add', 11, 1], ['add', 5, -1], ['sum', 1, 11], ['sum', 4, 12]];

function slowTotals(n, operations) {
  const rows = new Array(n + 1).fill(0);
  const out = [];
  for (const [op, a, b] of operations) {
    if (op === 'add') rows[a] += b;
    else {
      let total = 0;
      for (let r = a; r <= b; r++) total += rows[r];
      out.push(total);
    }
  }
  return out;
}

/** A seeded generator (Park and Miller), so every run checks the same cases. */
function generator(seed) {
  let s = seed;
  return (lo, hi) => {
    s = (s * 48271) % 2147483647;
    return lo + (s % (hi - lo + 1));
  };
}

test('the theatre of the lesson', () => {
  assert.deepEqual(rangeTotals(16, THEATRE), [7, 6]);
});

test('the first row, the last row and ranges of one row', () => {
  const ops = [['add', 1, 2], ['add', 9, 7], ['sum', 1, 1], ['sum', 9, 9], ['sum', 1, 9], ['sum', 2, 8]];
  assert.deepEqual(rangeTotals(9, ops), [2, 7, 9, 0]);
});

test('negative changes and the same row changed many times', () => {
  const ops = [['add', 4, 10], ['add', 4, -3], ['add', 4, -7], ['sum', 1, 6], ['add', 4, 1], ['sum', 4, 4]];
  assert.deepEqual(rangeTotals(6, ops), [0, 1]);
});

test('300 random cases, compared with a plain list', () => {
  const pick = generator(9);
  for (let t = 0; t < 300; t++) {
    const n = pick(1, 30);
    const ops = [];
    for (let j = 0, m = pick(1, 40); j < m; j++) {
      if (pick(0, 1) === 0) ops.push(['add', pick(1, n), pick(-5, 9)]);
      else {
        const left = pick(1, n);
        ops.push(['sum', left, pick(left, n)]);
      }
    }
    assert.deepEqual(rangeTotals(n, ops), slowTotals(n, ops), JSON.stringify([n, ops]));
  }
});

test('1,000 rows of up to 10**9 bookings: totals far above 2**31', () => {
  const pick = generator(4);
  const n = 1000;
  const ops = [];
  for (let row = 1; row <= n; row++) ops.push(['add', row, pick(100000000, 1000000000)]);
  ops.push(['sum', 1, n], ['sum', 1, n / 2], ['sum', n / 2 + 1, n]);
  const answers = rangeTotals(n, ops);
  assert.deepEqual(answers, slowTotals(n, ops));
  assert.ok(answers[0] > 2 ** 31, 'the total of all rows is above 2**31');
});
A hint

Keep a list tree of n + 1 zeros and write the two loops of the lesson: add climbs with i += i & -i while i <= n, and prefix descends with i -= i & -i while i > 0. A range total is prefix(right) - prefix(left - 1).

The sample tests run on this device, in your browser (Pyodide, QuickJS): nothing is sent to mysmartcopilot.com. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Programmer Calculator (Bitwise) Type a position in decimal and read it in binary: the lowest 1 bit is how many rows that position holds.

Check yourself

5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 5 Position 12 of a Fenwick tree holds the total of how many values?

    Type a number.

    Show the answer to question 1

    Answer: 4 values

    12 is 1100 in binary, so 12 & -12 = 4: position 12 holds the total of the 4 values that end at it, rows 9 to 12.

  2. Question 2 of 5 In a tree of 16 positions, which positions does add(6, x) change?

    Choose one answer.

    Show the answer to question 2

    Answer: 6, 8 and 16

    The climb adds the lowest set bit each time: 6 + 2 = 8, then 8 + 8 = 16, and 16 + 16 is past the end. Those are exactly the positions whose ranges contain row 6: 5 to 6, 1 to 8 and 1 to 16.

  3. Question 3 of 5 Which positions does prefix(13) read?

    Choose one answer.

    Show the answer to question 3

    Answer: 13, 12 and 8

    The descent removes the lowest set bit each time: 13 is 1101 in binary, so 13 − 1 = 12, 12 − 4 = 8 and 8 − 8 = 0. The three ranges, row 13, rows 9 to 12 and rows 1 to 8, split rows 1 to 13 without overlap.

  4. Question 4 of 5 At most how many positions can one prefix query read in a Fenwick tree of 1,000 positions?

    Type a number.

    Show the answer to question 4

    Answer: 9 positions

    prefix(i) reads one position for each set bit of i. No number up to 1,000 has more than 9 set bits, since the smallest with 10 is 1,023; 511 (111111111 in binary) is one with 9.

  5. Question 5 of 5 Why do Fenwick trees number their positions from 1?

    Choose one answer.

    Show the answer to question 5

    Answer: Because 0 & -0 is 0, so a climb or a descent from position 0 would never move

    Both loops move by the lowest set bit, and 0 has none. Code that stores values from 0 shifts every position by one before calling add and prefix.

Why the climb and the descent are right: the proofs, the steps counted and an O(n) build Pro: opens with a Pro, Premium or Ultimate pass

Both loops rest on one fact about binary numbers and one invariant each. This part proves them, counts the steps on a million positions and measures the O(n) build against n separate adds.

In this part:

  • Why i & -i is the lowest set bit: two’s complement, worked
  • The descent tiles 1 to i: the invariant and its proof
  • The climb reaches every range that contains i, and only those
  • Steps per operation, measured on a million positions

1 runnable example (Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Range updates, inversions and running percentiles, with run records Pro: opens with a Pro, Premium or Ultimate pass

The same two loops answer three more kinds of question once the right numbers are stored in the tree. This part derives each one, checks it against a plain list and runs it on up to a million values.

In this part:

  • Range updates and single rows: a tree over the differences
  • Range sums after range updates: two trees and the formula, worked
  • Inversions of a million numbers: a tree of counts against merge sort, measured
  • Running percentiles: compress the values, then walk down the bits

3 runnable examples (Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

The tree in five languages, with 64-bit totals and five bugs reproduced Pro: opens with a Pro, Premium or Ultimate pass

The tree is the same two short loops in every language, but each language has its own way to overflow or loop forever. This part runs one program in five languages, reproduces five bugs that pass a quick test, and says when a Fenwick tree is the wrong tool.

In this part:

  • One Fenwick tree, five languages, every output recorded
  • Five bugs that pass a quick test, reproduced
  • When a Fenwick tree is the wrong tool
  • Practice set

3 runnable examples (C++, Go, Java, JavaScript, Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

  1. Fresher to mid · Warm-up

    What does position i of a Fenwick tree store, and why does a prefix query read only O(log n) positions?

    Position i, counted from 1, stores the total of a range that ends at i and is as long as the lowest set bit of i, the value of i & -i. Odd positions hold one value, position 12 holds four and position 16 holds sixteen. A prefix query for the first i values adds tree[i], jumps to i - (i & -i), the position just before that range, and repeats until it reaches 0. The ranges it collects tile 1 to i without a gap or an overlap, and each jump clears one set bit of i, so the query reads one position per set bit: at most about log₂ n + 1 of them. An update climbs the other way, with i + (i & -i), through the positions whose ranges contain i.

  2. Mid to senior

    A live quiz has 500,000 players with integer scores from 0 to 10,000,000. Scores change about 20,000 times a second, and after every change the player sees their current rank. Which structure do you use, what does each change cost, and how much memory does it need?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    A billing service gives discounts to ranges of customer IDs from 1 to 2,000,000, thousands of times a day, and finance asks for the total discount given to any range of IDs at any moment. How do you make both operations O(log n), and how do you convince a reviewer that your formula is right?

    Model answer, Pro. First step and follow-ups

  4. Fresher to mid

    Your service keeps the prices of 1 million products in a Fenwick tree to answer range sums. Product now also wants the lowest price in any range of product IDs, and prices go both up and down. Can the Fenwick tree answer it? What would you use instead, and what does it cost?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A Fenwick tree is one list in which position i holds the total of the last i & -i values up to i.
  • add climbs with i += i & -i; prefix descends with i -= i & -i; both touch at most ⌊log₂ n⌋ + 1 positions.
  • A range total is prefix(r) - prefix(l - 1), so the operation must be one that can be undone, such as addition.
  • Positions start at 1, because 0 & -0 is 0.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.