Implement a Disjoint Set (Union-Find) over n elements 0 .. n-1:
union(a, b) — merge the sets containing a and b.connected(a, b) — return whether a and b are in the same set.components() — return the current number of disjoint sets.Aim for near-O(1) amortized operations using union by rank/size and path compression.
Input: DisjointSet(5), unite(0,1), unite(1,2), connected(0,2), components()
Output: null null null true 3
{0,1,2}, {3}, {4} — 0 and 2 share a set; three components remain.Input: DisjointSet(3), connected(0,1), unite(0,1), connected(0,1) Output: null false null true Initially separate, then merged.
- 1 <= n <= 10^5 - 0 <= a, b < n - At most 2 * 10^4 operations
A Disjoint Set maintains a forest: each element points to a parent, and the root of a tree is its set's representative. find walks to the root; union links one root under another; connected is find(a) == find(b); and a component counter starting at n drops by one on each successful union.
The naive version works but can degrade to O(n) per find on a tall chain. Two optimizations flatten it to near-O(1) amortized (inverse-Ackermann):
find, repoint nodes toward the root so future finds are short.Note the connectivity answers (connected, components) are the same regardless of the union heuristic — the heuristics only change the internal tree shape, not which elements share a set.
“What does components() count?”
The number of disjoint sets currently.
“Do union heuristics change the answers?”
No — only performance; connectivity is the same.
Each element points to a parent; find walks to the root, union links two roots, connected compares roots.
Path compression plus union by rank keep every operation near-constant amortized.
Worked example — n = 5, then unite(0,1), unite(1,2), connected(0,2), components()
after unions: {0,1,2}, {3}, {4}
connected(0,2) -> true
components() -> 3
connected(a,b) is find(a) == find(b).
Merging two distinct sets reduces components by one.
The two optimizations flatten the forest.
| Naive union | Rank + path compression | |
|---|---|---|
| find | O(n) worst case | near-O(1) amortized |
| union | O(n) worst case | near-O(1) amortized |
| Answers | same | same |
Both give identical connectivity answers; the optimizations only speed them up. Full code is in the Approaches selector below.
Key takeaway
A parent forest with find/union; connected compares roots and components counts sets. Path compression + union by rank make every operation near-O(1) amortized.
find: walk to root (compressing); union: link roots by rank, cnt--; connected: same root