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.

Coding Interview Patterns  Module 30 – Range queries: Fenwick and segment trees

Segment trees: point updates, range queries

The lowest fare between two days while fares keep changing: the problem with ties, worked examples, the brute force and why a tree of halves fits.

  • Advanced
  • 40 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 intro, Pro: 8 of 40 minutes free
Free: The problem: the cheapest fare between two days · The brute force: scan the range. With a pass: the other 15 sections and 3 interview answers, about 32 minutes.

What you will learn

  • Build an iterative segment tree of 2n nodes for a minimum, maximum or sum
  • Answer a range query and a point update in O(log n) each
  • Find where the minimum is, with a fixed rule for ties
  • Write a custom merge for a combine that is not commutative, such as the best run of values
  • Choose between the bottom-up and the recursive tree, with measured numbers

Before you start

What you’ll learn · Before you start

What you will learn

  • Build an iterative segment tree of 2n nodes for a minimum, maximum or sum
  • Answer a range query and a point update in O(log n) each
  • Find where the minimum is, with a fixed rule for ties
  • Write a custom merge for a combine that is not commutative, such as the best run of values
  • Choose between the bottom-up and the recursive tree, with measured numbers

Before you start

On this page

A segment tree answers range queries, such as the smallest value from position l to position r, while single values change, and both the query and the change take O(log n). It stores the answer for each half of the array, each quarter and so on down to single values. Any range is then the combination of at most about 2 log₂ n stored answers, and a change recomputes only the log₂ n answers above it. It works for any associative combine with an identity value: minimum, maximum, sum, gcd, or a custom merge such as the best run of consecutive values. Here it finds the cheapest fare between two days while fares change.

The problem: the cheapest fare between two days

The problem

A booking site keeps the cheapest fare of each of the next n days, days numbered from 0. Write a class FareTracker with update(day, fare), when one day’s fare changes, and cheapest(first, last), which returns the lowest fare of days first to last, both included, and the day it falls on; when several days share that fare, the earliest. Limits: 1 ≤ n ≤ 200,000, at most 200,000 calls, fares from 1 to 10⁹.

With the fares [450, 380, 520, 380, 610, 300, 700, 340]:

  1. cheapest(0, 4) gives (380, 1): days 1 and 3 tie, and day 1 is earlier.
  2. After update(1, 500), cheapest(0, 4) gives (380, 3) and cheapest(5, 7) gives (300, 5).
  3. After update(5, 900), cheapest(4, 7) gives (340, 7), and cheapest(2, 2) gives (520, 2).

The brute force: scan the range

Read every day of the range and keep the best pair (fare, day). A change is one write, and a question reads up to n fares:

Every range scanned, with the fares read counted Python · brute/brute_force.py
"""The cheapest fare over a range of days, found by scanning the range for every question, with the work counted."""
import random


def run_brute(fares, events):
    fares, answers, reads = list(fares), [], 0
    for kind, x, y in events:
        if kind == "update":
            fares[x] = y
        else:
            best = min((fares[d], d) for d in range(x, y + 1))  # the earliest day wins a tie
            reads += y - x + 1
            answers.append(best)
    return answers, reads


fares = [450, 380, 520, 380, 610, 300, 700, 340]
events = [("cheapest", 0, 4), ("update", 1, 500), ("cheapest", 0, 4), ("cheapest", 5, 7),
          ("update", 5, 900), ("cheapest", 4, 7), ("cheapest", 2, 2)]
print("fares", fares)
print("answers (fare, day):", run_brute(fares, events)[0])

rng = random.Random(8)
last = None
for n in (1_000, 2_000, 4_000):
    fares = [rng.randint(100, 5000) for _ in range(n)]
    events = []
    for _ in range(n):
        if rng.random() < 0.5:
            events.append(("update", rng.randrange(n), rng.randint(100, 5000)))
        else:
            a, b = rng.randrange(n), rng.randrange(n)
            events.append(("cheapest", min(a, b), max(a, b)))
    reads = run_brute(fares, events)[1]
    print(f"n = {n:,} days and {n:,} events: {reads:,} fares read" + (f", x{reads / last:.1f}" if last else ""))
    last = reads
print(f"at 200,000 days and 200,000 events: about {last * (200_000 / 4_000) ** 2:.1e} fares read (estimated)")

Output

fares [450, 380, 520, 380, 610, 300, 700, 340]
answers (fare, day): [(380, 1), (380, 3), (300, 5), (340, 7), (520, 2)]
n = 1,000 days and 1,000 events: 172,828 fares read
n = 2,000 days and 2,000 events: 654,789 fares read, x3.8
n = 4,000 days and 4,000 events: 2,607,959 fares read, x4.0
at 200,000 days and 200,000 events: about 6.5e+09 fares read (estimated)

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

Doubling the days and the events multiplies the reads by about 4. At the limits that is about 6.5 × 10⁹ fares read, far beyond a time limit of a few seconds in any language.

What to notice

Three faster structures fail here, each for one reason. Prefix sums and Fenwick trees answer a range by subtracting two prefixes, and a minimum cannot be subtracted. A sparse table answers fixed data, and these fares change. What the minimum does have is associativity, with an identity, +∞, which leaves no mark on an answer: the cheapest of a range is the cheaper of the cheapest of its two halves. So store the answer of every half, quarter and single day.

A tree of 15 nodes over 8 days of fares, each node holding the cheapest fare below it; days 1 to 5 use nodes 9, 5 and 6.A segment tree over 8 days of faresNode k holds the cheaper of nodes 2k and 2k + 1#1300 on day 5#2380 on day 1#3300 on day 5#4380 on day 1#5380 on day 3#6300 on day 5#7340 on day 7#8450#9380#10520#11380#12610#13300#14700#15340day 0day 1day 2day 3day 4day 5day 6day 7Days 1 to 5 combine nodes 9, 5 and 6:the cheapest fare is 300, on day 5.

The cheapest fare of every half, quarter and day

Text description of the diagram

The fares of days 0 to 7 are 450, 380, 520, 380, 610, 300, 700 and 340. They sit in the bottom row, in nodes 8 to 15. Each node above holds the cheaper of its two children, with the day of that fare, a tie going to the earlier day.

  1. Nodes 4 to 7 cover two days each: node 4 holds 380 on day 1, node 5 holds 380 on day 3, node 6 holds 300 on day 5 and node 7 holds 340 on day 7.
  2. Nodes 2 and 3 cover four days each: node 2 holds 380 on day 1 and node 3 holds 300 on day 5.
  3. Node 1, at the top, covers all eight days and holds 300 on day 5.

Three nodes are shaded: node 9, which is day 1; node 5, days 2 and 3; and node 6, days 4 and 5. Together they cover days 1 to 5 exactly, so the cheapest fare of those days is the cheapest of their three answers: 300, on day 5.

The tree can be written bottom-up, in one array of 2n nodes, or recursively from the root, which is easier to extend. A tree of minimums can also search: it finds the first day from a given day whose fare fits a budget.

The bottom-up tree, step by step Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Why the query takes exactly the days of the range: the invariant
  • Dry run: days 1 to 5 on the example
  • The cost
  • FareTracker in five languages
    • Checked against a scan on 3,000 random lists
  • Custom merges: the best run of consecutive days
  • Bottom-up or recursive: measured
  • Six bugs reproduced
  • Variants
  • Edge cases
  • Follow-up questions, answered
  • Exercise
    • The cheapest fare over a range of days
  • A practice set with sample answers
  • Check yourself
  • Interview questions
    • How many nodes does a bottom-up segment tree over n values use, and where in its array do the values sit?
    • A fare tracker answers range minimums with a segment tree. Product now wants, 100,000 times a day between fare changes, the earliest day at or after day s whose fare is within a traveller's budget. How do you answer it in O(log n), and how do you know it is O(log n)?
    • A segment tree returns the best total of consecutive days in a range. It passes every test written by hand, but a random test finds wrong answers for some ranges. The merge function is correct. What do you look at first, and how do you fix it?
    • A monitoring service keeps 10⁷ sensor readings and must handle 10⁷ point updates and 10⁷ range-minimum queries a day, within 512 MB. Which segment-tree layout and integer width do you choose, and what do the memory and time come to?
  • Key takeaways

7 runnable examples (C++, Go, Java, JavaScript, Python, Python (recursive.py), Python (timing.py)) · 1 exercise with sample tests · 3 interview answers · about 25 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).

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.