Coding Interview Patterns Module 22 – Union-find (disjoint sets)
Offline queries sorted by threshold
Answer many questions of whether two places connect on links below a limit by sorting links and questions together: the problem, brute force and signal.
What you will learn
- Sort links and questions by threshold and answer every question in one pass
- Answer whether two places connect using only links below a limit
- Write each answer back in the order the questions were asked
- Prove that the union-find holds exactly the links below the current limit
- Answer the same questions one at a time with a merge tree built from the links
Before you start
What you’ll learn · Before you start
What you will learn
- Sort links and questions by threshold and answer every question in one pass
- Answer whether two places connect using only links below a limit
- Write each answer back in the order the questions were asked
- Prove that the union-find holds exactly the links below the current limit
- Answer the same questions one at a time with a merge tree built from the links
Before you start
On this page
Offline queries sorted by threshold answer many questions of one kind at once: can u reach v using only links whose weight is below a limit? When every question is known before the first answer is due, the questions may be answered in any order. Sorted by limit, each question allows every link the previous one allowed and perhaps more, so a single union-find can grow from one question to the next instead of being rebuilt, and each answer is two finds. When the questions arrive one at a time instead, a merge tree built from the sorted links answers each one alone. A search per question, the obvious method, does the whole network’s work again for every question.
The problem: buses on flooded roads
After a storm, a city’s roads are flooded to different depths. The city has n junctions, numbered 0 to n − 1, and
roads (a, b, depth) that join two junctions both ways. A bus with a limit of L centimetres can use a road only when
the water on it is strictly below L. Each question (u, v, L) asks whether a bus with limit L can get from u to v;
answer them all, in the order they were asked. Limits: n, the number of roads and the number of questions are each at
most 100,000, and depths and limits are at most 10⁹.
- Roads 0–1 (30), 1–2 (10), 2–3 (45), 0–3 (60) and 3–4 (20) answer (0, 2, 31), (0, 3, 45), (0, 3, 46), (2, 4, 21) and (4, 4, 1) with yes, no, yes, no and yes. A limit of 45 is not enough for the 45 cm road, and a bus is always at its own junction.
- Two roads between 0 and 1, of 50 and 20 cm, answer (0, 1, 21) with yes and (1, 0, 20) with no.
The first example, for a bus with a limit of 46 cm
Text description of the diagram
Junctions 0, 1 and 2 are in a row along the top, and junctions 4 and 3 along the bottom, with 3 under 2. The roads and the water on them:
- 0 to 1: 30 cm.
- 1 to 2: 10 cm.
- 2 to 3: 45 cm.
- 0 to 3, a long diagonal: 60 cm.
- 3 to 4: 20 cm.
For a bus with a limit of 46 cm, every road with water below 46 cm is drawn solid and the 60 cm road is dashed, because it is too deep. The bus can go from 0 to 3 through 1 and 2. With a limit of 45 cm it could not: the 2 to 3 road is exactly 45 cm deep, and a road as deep as the limit is not usable.
The brute force, and why it fails at this size
For each question, search from u over the roads whose water is below its limit, and see whether the search reaches v:
# The brute force: for every question, a breadth-first search over the roads whose water is below the bus's limit.
# Correct by construction, so it is what faster code is checked against.
import random
from collections import deque
def can_travel_brute(n, roads, questions):
"""For each question (u, v, limit): can a bus reach v from u on roads whose depth is strictly below the limit?"""
links = [[] for _ in range(n)]
for a, b, depth in roads:
links[a].append((b, depth))
links[b].append((a, depth))
answers, looks = [], 0
for u, v, limit in questions:
seen, queue = {u}, deque([u])
while queue and v not in seen:
for w, depth in links[queue.popleft()]:
looks += 1
if depth < limit and w not in seen:
seen.add(w)
queue.append(w)
answers.append(v in seen)
return answers, looks
roads = [(0, 1, 30), (1, 2, 10), (2, 3, 45), (0, 3, 60), (3, 4, 20)]
questions = [(0, 2, 31), (0, 3, 45), (0, 3, 46), (2, 4, 21), (4, 4, 1)]
answers, looks = can_travel_brute(5, roads, questions)
print("example 1:", answers, f"after {looks} road looks")
answers, looks = can_travel_brute(2, [(0, 1, 50), (0, 1, 20)], [(0, 1, 21), (1, 0, 20)])
print("example 2:", answers, f"after {looks} road looks")
rng = random.Random(15)
for n in (500, 1_000, 2_000):
roads = [(i, rng.randrange(i), rng.randint(1, 100)) for i in range(1, n)] # a random tree of roads
questions = [(rng.randrange(n), rng.randrange(n), 101) for _ in range(n)] # every road usable: worst case
_, looks = can_travel_brute(n, roads, questions)
print(f"{n:>5,} junctions, roads and questions: {looks:>9,} road looks") Output
example 1: [True, False, True, False, True] after 20 road looks example 2: [True, False] after 4 road looks 500 junctions, roads and questions: 210,211 road looks 1,000 junctions, roads and questions: 890,910 road looks 2,000 junctions, roads and questions: 3,557,726 road looks
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
When every road is usable, each search can cover the whole network, so doubling n and the number of questions multiplies the work by four. At the limits that is up to 10⁵ questions × 2 × 10⁵ road looks, about 2 × 10¹⁰: hours of Python, and still minutes of C++.
What to notice
- All the questions are given before any answer is needed, so they may be answered in any order.
- A higher limit never removes a road: the roads usable at one limit are usable at every higher one.
- A question needs only whether two junctions are connected, which is what a union-find answers.
Sorting both lists, step by step Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- Why every answer is right
- A dry run on the first example
- The code in five languages
- The sorted pass against a search per question, measured
- Edge cases and common bugs
- When questions arrive one at a time
- Variants: junctions reached, and limits that run the other way
- Checked against a search per question
- Exercise: journeys below a limit
- Journeys on roads below a limit
- A practice set with sample answers
- Check yourself
- Interview questions
- Why does sorting the questions by their limit let one union-find answer all of them, and why must each question keep its original position?
- The bus questions now arrive one at a time on a dispatch screen, and each must be answered before the next is read. The roads are known in advance: 10⁵ junctions, 10⁵ roads and 10⁵ questions. What do you build, and what does each question cost?
- For each of 10⁵ pairs of junctions, report the smallest whole-number limit that lets a bus travel between them, or −1 if no limit does. How do you answer all the pairs without a search for each?
- For each of 10⁵ limits, how many unordered pairs of different junctions could a bus with that limit travel between? The network has 10⁵ junctions and 2 × 10⁵ roads.
- Key takeaways
8 runnable examples (C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 23 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
- Efficiency of a Good But Not Linear Set Union Algorithm (R. E. Tarjan, Journal of the ACM 22(2), 1975) (Association for Computing Machinery (ACM))
- On the shortest spanning subtree of a graph and the traveling salesman problem (J. B. Kruskal, Proceedings of the AMS 7(1), 1956) (American Mathematical Society (AMS))
- Sorting techniques (Python HOWTOs) (Python Software Foundation)
- Array.prototype.toSorted() (MDN Web Docs)
- Class Arrays (Java SE 25 API) (Oracle)
- std::ranges::sort (cppreference.com) (cppreference.com)
- Package slices: SortFunc (Go standard library) (The Go Authors)
- Dictionary of Algorithms and Data Structures: tree (National Institute of Standards and Technology (NIST))
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress