Coding Interview Patterns Module 26 – Dynamic programming: advanced patterns
Rerooting: answers for every root
The total distance from every village to all others in a tree of roads: the problem, a search from every village, and the signal for rerooting.
What you will learn
- Compute an answer for every possible root of a tree in O(n) in all
- Combine a first pass that gathers subtree facts and a second pass that moves the root one edge at a time
- Apply rerooting to sums of distances, to road lengths and to villages with populations
- Compute the farthest node from every node, where a maximum cannot be undone like a sum
- Choose the village that minimises total travel, and prove which one it is
Before you start
What you’ll learn · Before you start
What you will learn
- Compute an answer for every possible root of a tree in O(n) in all
- Combine a first pass that gathers subtree facts and a second pass that moves the root one edge at a time
- Apply rerooting to sums of distances, to road lengths and to villages with populations
- Compute the farthest node from every node, where a maximum cannot be undone like a sum
- Choose the village that minimises total travel, and prove which one it is
Before you start
On this page
Rerooting answers a question for every node of a tree as if that node were the root, in O(n) in all instead of O(n) per root. A first pass from one fixed root gathers facts about every subtree; a second pass moves the root across one edge at a time and adjusts the answer in O(1) per edge. The standard example is the sum of distances in a tree: for every node, the total distance to all the other nodes. A search from every node takes O(n²); rerooting takes two passes.
Total distances in a district of villages
A district has n villages joined by n − 1 roads, each 1 km long, so that there is exactly one route between any two villages. For every village, return the total distance from it to all the other villages. Limits: 1 ≤ n ≤ 10⁵, and the roads come as pairs of village numbers, in any order and direction.
- The seven villages, with roads 0–1, 1–2, 1–3, 3–4, 3–5 and 5–6:
[15, 10, 15, 9, 14, 12, 17]. Village 3 is the best meeting point. - A line of four villages:
[6, 4, 4, 6]. - A star of five villages around village 0:
[4, 7, 7, 7, 7].
The seven villages, split by the road from village 1 to village 3
Text description of the diagram
The diagram shows seven villages joined by six roads of 1 km each, drawn as a tree with village 0 at the top. Every village is labelled with its total distance to the other six.
- Village 0 (15 km) is joined to village 1 (10 km).
- Village 1 is joined to village 2 (15 km) and, by a thick road, to village 3 (9 km).
- Village 3 is joined to village 4 (14 km) and village 5 (12 km), and village 5 to village 6 (17 km).
Villages 3, 4, 5 and 6, the four on village 3's side of the thick road, have dashed borders, and village 3 also has a double border.
Moving the meeting point from village 1 to village 3 brings the four villages of 3's side 1 km closer and takes the three villages of the other side 1 km farther, so village 3's total is village 1's total, 10, minus 4, plus 3: 9, the smallest of all seven.
The brute force: a search from every village
A breadth-first search from one village finds its distance to every other village in O(n), so n searches give every total in O(n²).
free/brute_force.py
"""The total distance from every village to all others by brute force: one breadth-first search per village."""
import random
from villages import distances_from, neighbours, random_district
def totals_brute(n, roads):
near = neighbours(n, roads)
visits = 0
totals = []
for start in range(n):
dist = distances_from(near, start)
visits += n # one search reaches every village once
totals.append(sum(dist))
return totals, visits
DISTRICTS = {
"the seven villages": (7, [(0, 1), (1, 2), (1, 3), (3, 4), (3, 5), (5, 6)]),
"a line of four villages": (4, [(0, 1), (1, 2), (2, 3)]),
"a star of five villages around village 0": (5, [(0, 1), (0, 2), (0, 3), (0, 4)]),
"one village": (1, []),
}
for label, (n, roads) in DISTRICTS.items():
totals, visits = totals_brute(n, roads)
print(f"{label}: {totals} after {visits:,} village visits")
rng = random.Random(14)
for n in (500, 1_000, 2_000):
_, visits = totals_brute(n, random_district(rng, n))
print(f"a random district of {n:,} villages: {visits:,} village visits") Output
the seven villages: [15, 10, 15, 9, 14, 12, 17] after 49 village visits a line of four villages: [6, 4, 4, 6] after 16 village visits a star of five villages around village 0: [4, 7, 7, 7, 7] after 25 village visits one village: [0] after 1 village visits a random district of 500 villages: 250,000 village visits a random district of 1,000 villages: 1,000,000 village visits a random district of 2,000 villages: 4,000,000 village visits
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 brute_force.py
free/villages.py
"""Districts of villages joined by roads of 1 km that form a tree: neighbour lists, searches and random districts."""
from collections import deque
def neighbours(n, roads):
"""The villages one road away from each village."""
near = [[] for _ in range(n)]
for u, v in roads:
near[u].append(v)
near[v].append(u)
return near
def distances_from(near, start):
"""Breadth-first search from one village: the distance in km to every village."""
dist = [-1] * len(near)
dist[start] = 0
queue = deque([start])
while queue:
u = queue.popleft()
for v in near[u]:
if dist[v] < 0:
dist[v] = dist[u] + 1
queue.append(v)
return dist
def random_district(rng, n):
"""n villages with random names 0..n-1; each joins an earlier one; roads in random order and direction."""
label = list(range(n))
rng.shuffle(label)
roads = [(label[rng.randrange(v)], label[v]) for v in range(1, n)]
roads = [(v, u) if rng.random() < 0.5 else (u, v) for u, v in roads]
rng.shuffle(roads)
return roads 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
The visits come to exactly n²: 4,000,000 for 2,000 villages. At 10⁵ villages that is 10¹⁰ visits, more than a minute even in compiled code and hours in Python.
What to notice
- Moving the meeting point along one road changes every distance by exactly 1 km. Going from village p to its neighbour c, every village on c’s side of the road gets 1 km closer and every other village 1 km farther.
- So c’s total follows from p’s total in one step, once you know how many villages lie on c’s side: on the diagram, 10 − 4 + 3 = 9.
- Those counts are subtree sizes, and one pass from any fixed village gives them for every road at once.
- Answers built from a maximum fit the same shape, such as the farthest village from each village, with more bookkeeping, because a maximum cannot be undone the way a sum can.
Two passes: sizes on the way up, totals on the way down Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- Why one road changes the total by n − 2 · size(c), proved
- A dry run on the seven villages
- The code in Python, JavaScript, Java, C++ and Go
- Totals that need 64 bits
- A C++23 trap: a helper named print
- Measured: two passes against a search from every village
- When nothing can be subtracted: the farthest village
- Edge cases and five common bugs
- Variants: road lengths, people and the best village for a clinic
- Exercise: the total distance from every village
- The total distance from every village
- A practice set with answers
- Check yourself
- Follow-up questions, answered
- Interview questions
- A tree network has 200,000 nodes. For every node, report the sum of its distances to all other nodes, in O(n) in total. Derive how the answer changes across one edge, and say which integer types you need.
- For every node of a tree with 100,000 nodes, find the distance to its farthest node, in O(n). Explain why the subtraction that works for sums of distances does not work here, and what you keep instead.
- Villages with populations form a tree of roads with lengths. Choose the village for one clinic that minimises the total person-km travelled. Show how to compute it in O(n), and prove which village wins without comparing every total.
- Key takeaways
9 runnable examples (C++, Go, Java, JavaScript, Python, distances.py, drills.py, farthest.py, villages.py) · 1 exercise with sample tests · 3 interview answers · about 22 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
- NIST Dictionary of Algorithms and Data Structures: rooted tree (National Institute of Standards and Technology (NIST))
- NIST Dictionary of Algorithms and Data Structures: breadth-first search (National Institute of Standards and Technology (NIST))
- collections.deque (The Python Standard Library) (Python Software Foundation)
- The Java Language Specification, Java SE 25: 15.18.2 Additive operators (+ and -) for numeric types (Oracle)
- Arithmetic operators: overflows (cppreference.com) (cppreference.com)
- std::print (cppreference.com) (cppreference.com)
- Argument-dependent lookup (cppreference.com) (cppreference.com)
- The Go Programming Language Specification: integer overflow (The Go Authors)
- Number.MAX_SAFE_INTEGER (MDN Web Docs)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress