Coding Interview Patterns Module 30 – Range queries: Fenwick and segment trees
Sparse tables: range minimum and LCA in O(1)
The lowest temperature of any span of a finished season, and the deepest folder that holds two files: the problems, examples, brute forces and the signal.
What you will learn
- Build a sparse table in O(n log n) and answer a range minimum in O(1)
- Prove that two overlapping blocks give the right minimum, and show why a sum breaks
- Convert lowest-common-ancestor questions into range minimums over an Euler tour
- Build the Euler tour without recursion, for chains of 100,000 folders and more
- Compare the Euler tour with binary lifting and with walking up, measured on two shapes of tree
Before you start
What you’ll learn · Before you start
What you will learn
- Build a sparse table in O(n log n) and answer a range minimum in O(1)
- Prove that two overlapping blocks give the right minimum, and show why a sum breaks
- Convert lowest-common-ancestor questions into range minimums over an Euler tour
- Build the Euler tour without recursion, for chains of 100,000 folders and more
- Compare the Euler tour with binary lifting and with walking up, measured on two shapes of tree
Before you start
On this page
A sparse table answers the minimum of any range of fixed data in O(1): it stores the minimum of every block whose length is a power of two, and any range is covered by two such blocks that may overlap, which a minimum allows. The same table answers lowest-common-ancestor questions on a fixed tree in O(1) each, through an Euler tour of the tree. This lesson works both on two original problems: the lowest temperature of a finished season, and the deepest folder that holds two files.
Two problems on data that never changes
Problem A: the coldest span of a season. A weather station’s season is over: lows[d] is the lowest temperature
of day d, in whole degrees, possibly negative. Answer many questions “what was the lowest temperature from day l to
day r?”. With lows = [4, 2, 7, -1, 3, -1, 6, 5], days 0 to 7 give −1, days 0 to 2 give 2, days 4 to 7 give −1 and
days 6 and 7 give 5.
Problem B: the deepest common folder. A shared drive has n folders; parent[v] holds folder v, and the root
has −1. For a pair of folders, find the deepest folder that contains both, their lowest common ancestor (LCA), and the
number of cd steps from one to the other. In the tree of the diagram below, drafts and specs meet in docs, 3 steps
apart; ui and tests in src; drafts and ui only in the root; and app contains ui itself.
Limits for both: up to 200,000 values or folders and 200,000 questions.
The brute force: scan the span, walk up the tree
For A, scan the days of each span. For B, walk the deeper folder up to the other’s depth, then both up together until they meet:
"""Both problems the slow way, with the work counted: scan every span of days, and walk up from both folders."""
import random
lows = [4, 2, 7, -1, 3, -1, 6, 5]
spans = [(0, 7), (0, 2), (4, 7), (6, 7)]
print("A: daily lows", lows)
print(" lowest of each span:", [min(lows[a : b + 1]) for a, b in spans])
parent = [-1, 0, 0, 1, 1, 2, 2, 5, 3] # 0 is the root folder
names = ["/", "docs", "src", "notes", "specs", "app", "tests", "ui", "drafts"]
def depth_of(parent):
depth = [0] * len(parent)
for v in range(len(parent)): # here every parent has a smaller number than its child
if parent[v] >= 0:
depth[v] = depth[parent[v]] + 1
return depth
def walk_up(parent, depth, a, b):
steps = 0
while depth[a] > depth[b]:
a, steps = parent[a], steps + 1
while depth[b] > depth[a]:
b, steps = parent[b], steps + 1
while a != b:
a, b, steps = parent[a], parent[b], steps + 2
return a, steps
depth = depth_of(parent)
print("B: deepest common folder of each pair")
for a, b in [(8, 4), (7, 6), (8, 7), (5, 7), (4, 4)]:
print(f" {names[a]} and {names[b]}: {names[walk_up(parent, depth, a, b)[0]]}")
rng = random.Random(6)
for shape, n in (("random tree", 20_000), ("chain", 3_000)):
parent = [-1] + [rng.randrange(v) if shape == "random tree" else v - 1 for v in range(1, n)]
depth = depth_of(parent)
steps = sum(walk_up(parent, depth, rng.randrange(n), rng.randrange(n))[1] for _ in range(n))
print(f"{shape} of {n:,} folders, {n:,} questions: {steps:,} steps up, {steps / n:,.1f} a question")
print("a chain of 200,000 folders: about 200,000 / 3 = 66,667 steps a question, so about 1.3e+10 for 200,000"
" questions (estimated)") Output
A: daily lows [4, 2, 7, -1, 3, -1, 6, 5] lowest of each span: [-1, 2, -1, 5] B: deepest common folder of each pair drafts and specs: docs ui and tests: src drafts and ui: / app and ui: app specs and specs: specs random tree of 20,000 folders, 20,000 questions: 351,966 steps up, 17.6 a question chain of 3,000 folders, 3,000 questions: 2,990,113 steps up, 996.7 a question a chain of 200,000 folders: about 200,000 / 3 = 66,667 steps a question, so about 1.3e+10 for 200,000 questions (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
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
A scan of a random span reads about a third of the season, about 66,000 days at the limit, for each question. Walking up costs the depth of the tree: 17.6 steps a question on a random tree, but about n / 3 on a chain, where every folder holds the next. A chain of 200,000 folders needs about 1.3 × 10¹⁰ steps for 200,000 questions.
What to notice
Nothing changes after the data is read, so slow preparation is fine if every question becomes cheap. For A, a minimum does not mind counting a day twice, so two overlapping blocks of a power-of-two length can cover any span. For B, write down the folders in the order a depth-first walk visits them, each time it is at one: between the first visits of two folders, the walk stays inside their LCA’s subtree and passes the LCA itself, so the shallowest folder there is the answer, which is problem A on the tour’s depths.
The deepest common folder of drafts and specs, read off the tour
Text description of the diagram
The tree has 9 folders. Folder 0 (/) holds folders 1 (docs) and 2 (src). Docs holds 3 (notes) and 4 (specs); src holds 5 (app) and 6 (tests). Notes holds 8 (drafts) and app holds 7 (ui).
A depth-first walk that writes a folder down each time it is at that folder gives the tour 0, 1, 3, 8, 3, 1, 4, 1, 0, 2, 5, 7, 5, 2, 6, 2, 0, positions 0 to 16. Under each entry is its depth: 0, 1, 2, 3, 2, 1, 2, 1, 0, 1, 2, 3, 2, 1, 2, 1, 0.
Folder 8 is first visited at position 3 and folder 4 at position 6. Positions 3 to 6 are shaded: folders 8, 3, 1 and 4, at depths 3, 2, 1 and 2. The shallowest of them is folder 1 at depth 1, outlined, and folder 1, docs, is the deepest folder that holds both drafts and specs.
The main alternative for problem B is binary lifting, which stores each folder’s ancestors 1, 2, 4 and more levels up and answers a question in O(log n) steps.
The sparse table, step by step Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- Why two overlapping blocks give the right minimum: the proof
- The cost, with numbers
- LCA by Euler tour, step by step
- Why the shallowest folder between the first visits is the LCA: the proof
- A dry run on the folder tree
- The code in five languages
- Checked against walking up, on 3,000 random trees
- Binary lifting instead, measured
- Seven bugs reproduced
- Variants
- Edge cases
- Follow-up questions, answered
- Exercise
- The deepest common folder of many pairs
- A practice set with sample answers
- Check yourself
- Interview questions
- What is the Euler tour of a tree, and how does it turn a lowest-common-ancestor question into a range minimum?
- A log of 10⁷ 32-bit readings is frozen at midnight. Overnight, analysts run 10⁸ questions of three kinds over ranges: the maximum, the gcd and the sum. The machine has 1 GB. Which structures do you build, and how much memory and time do they take?
- In a 45-minute round you must answer 10⁶ lowest-common-ancestor questions on a fixed tree of 10⁵ nodes, given as a parent array. Which method do you write, the Euler tour with a sparse table, binary lifting or walking up, and how do you defend the choice?
- Depots form a tree of 10⁵ roads. Three couriers at depots a, b and c want to meet at the depot that makes their total travel smallest. Answer 10⁵ such questions, each with the meeting depot and the total distance. How, and why is your meeting depot right?
- Key takeaways
7 runnable examples (C++, Go, Java, JavaScript, Python, Python (check.py), Python (lifting.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.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
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
- The LCA Problem Revisited (M. A. Bender and M. Farach-Colton, LATIN 2000, LNCS 1776, pages 88–94) (Springer)
- Fast Algorithms for Finding Nearest Common Ancestors (D. Harel and R. E. Tarjan, SIAM Journal on Computing 13(2), 1984) (Society for Industrial and Applied Mathematics)
- sys.getrecursionlimit() (The Python Standard Library) (Python Software Foundation)
- collections.deque (The Python Standard Library) (Python Software Foundation)
- time.process_time() (The Python Standard Library) (Python Software Foundation)
- Int32Array (JavaScript reference) (MDN Web Docs)
- JEP 330: Launch Single-File Source-Code Programs (OpenJDK)
- The Go Programming Language Specification: Min and max (The Go Authors)
- AddressSanitizer (Clang documentation) (LLVM Project)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress