Union-Find Pattern
The Union-Find (Disjoint Set Union) pattern maintains a partition of N elements into non-overlapping groups, supporting two operations: find(x) returns the group representative, and union(x, y) merges two groups. With path compression and union-by-rank, both operations run in near-constant amortized time, formally O(alpha(N)) where alpha is the inverse Ackermann function.
By Alex Chen, Founder, InterviewChamp.AI · Last verified
Complexity
- Time
- O(alpha(N))
- Space
- O(N)
When to use Union-Find
- The problem asks for the number of connected components, the size of the largest group, or whether two elements share a group.
- Edges arrive one at a time and you need to detect when adding one would create a cycle (Kruskal's MST, Redundant Connection).
- You need to merge sets dynamically and answer membership queries efficiently, where rebuilding the graph after every update would be too slow.
- The problem can be reframed as equivalence classes — accounts that share an email, islands that touch, friends-of-friends groups.
- An undirected graph is sparse and you want O(E * alpha(N)) instead of O(V + E) BFS/DFS, especially when edges stream in.
Template
class UnionFind {
constructor(n) {
this.parent = Array.from({ length: n }, (_, i) => i);
this.rank = new Array(n).fill(0);
this.count = n;
}
find(x) {
if (this.parent[x] !== x) {
this.parent[x] = this.find(this.parent[x]);
}
return this.parent[x];
}
union(x, y) {
const rx = this.find(x);
const ry = this.find(y);
if (rx === ry) return false;
if (this.rank[rx] < this.rank[ry]) [this.parent[rx], this.parent[ry]] = [ry, rx];
else if (this.rank[rx] > this.rank[ry]) this.parent[ry] = rx;
else { this.parent[ry] = rx; this.rank[rx]++; }
this.count--;
return true;
}
}Example LeetCode problems
| # | Problem | Difficulty | Frequency |
|---|---|---|---|
| 200 | Number of Islands | medium | foundational |
| 323 | Number of Connected Components in an Undirected Graph | medium | foundational |
| 684 | Redundant Connection | medium | frequently asked |
| 721 | Accounts Merge | medium | company favorite |
| 261 | Graph Valid Tree | medium | classic |
| 547 | Friend Circles (Number of Provinces) | medium | frequently asked |
| 947 | Most Stones Removed with Same Row or Column | medium | company favorite |
Common mistakes
- Implementing find() without path compression, which degrades the worst-case operation cost to O(N) and causes TLE on tight time limits.
- Implementing union() without union-by-rank or union-by-size, producing chain-shaped trees that defeat path compression's amortized bound.
- Forgetting to call find() on both operands before comparing them in union() — using raw parent[x] gives the immediate parent, not the root.
- On Accounts Merge, indexing the union-find by account name rather than by a stable email-to-index map, which causes silent merges across distinct people with the same name.
- Decrementing the component count even when the two endpoints already share a root, which understates the final component count.
Frequently asked questions
When should I use Union-Find vs BFS/DFS?
Use Union-Find when edges arrive incrementally and you need to answer connectivity or cycle-detection queries between updates. Use BFS/DFS when the graph is given upfront and you walk it once. Union-Find is also the standard data structure for Kruskal's minimum spanning tree.
How does path compression actually work?
During find(x), after locating the root, point every node along the path directly to the root. The next find() on any of those nodes returns in one step. Combined with union-by-rank, this drops the amortized cost per operation to O(alpha(N)) — effectively constant for any practical N.
Can Union-Find handle deletions or edge removals?
Not natively — it's a build-up data structure. To support deletions you either rebuild after each deletion (expensive) or process the operations offline in reverse, adding edges from last to first while answering queries about the final state.
Why does Redundant Connection use Union-Find instead of cycle detection by DFS?
Both work, but Union-Find is cleaner: process edges one by one, and the first edge whose endpoints already share a root is the redundant one. DFS would require a re-traversal after each edge add. The Union-Find solution is O(E * alpha(N)) and reads in a few lines.
Solve it now
Free. No sign-up. Python and JavaScript run instantly in your browser.
Practice these live with InterviewChamp.AI
Drill the Union-Find pattern under real interview conditions with instant feedback on your reasoning, complexity claims, and code.
Practice these live with InterviewChamp.AI →