Overview
Union Find (disjoint set union, DSU) tracks which elements belong to the same group as groups merge. It supports two operations: find(x) returns the representative (root) of x's group, and union(a, b) merges the groups of a and b. With path compression and union by rank, both run in nearly O(1) amortized time.
It shines when connections arrive over time and you repeatedly ask 'are these connected?' or 'how many groups are there?' It is also the core of Kruskal's minimum spanning tree algorithm and a clean way to detect cycles in undirected graphs.
Every club has a president. To know if two people are in the same club, ask each for their president. When two clubs merge, one president reports to the other. Path compression is everyone learning the top president's name directly so future questions are instant.
When to use it
- Dynamic connectivity: edges are added and you query whether two nodes are connected.
- Counting connected components as edges arrive.
- Detecting a cycle when adding undirected edges (redundant connection).
- Grouping equivalent items: accounts with shared emails, similar strings, equations.
- Kruskal's minimum spanning tree.
Problem patterns it solves
Recognize it when: number of provinces, groups, or islands after unions.
- 547. Number of Provinces
- 323. Number of Connected Components
- 305. Number of Islands II
Recognize it when: the edge that connects two already-connected nodes.
- 684. Redundant Connection
- 261. Graph Valid Tree
- 685. Redundant Connection II
Recognize it when: merge items that share an attribute.
- 721. Accounts Merge
- 839. Similar String Groups
- 990. Satisfiability of Equality Equations
- 1202. Smallest String With Swaps
Recognize it when: connect everything with minimum total cost.
- 1584. Min Cost to Connect All Points
- 1135. Connecting Cities With Minimum Cost
Recognize it when: answer 'connected with limit x' queries by sorting edges and queries.
- 1697. Checking Existence of Edge Length Limited Paths
- 1631. Path With Minimum Effort (DSU variant)
Where it is used in real software
Determining whether two machines can communicate as links come online, and counting isolated network segments.
Connected-component labeling merges neighboring pixels with similar colors into regions.
Merging user records that share an email, phone, or device into a single customer profile.
Type inference (unification) merges type variables that must be equal, using union-find.
Key terms
- parent[x]
- The node x points to; a root points to itself.
- find(x)
- Follow parents to the root.
- Path compression
- During find, point every visited node directly at the root.
- Union by rank / size
- Attach the smaller tree under the larger to keep trees shallow.
- Inverse Ackermann alpha(n)
- The amortized cost per operation; at most 4 for any practical n.
How it works, step by step
- 1Initialize
parent[i] = i and size[i] = 1 for every element. components = n.
- 2find(x)
If parent[x] !== x, set parent[x] = find(parent[x]) (path compression) and return it.
- 3union(a, b)
Find both roots. If equal, they are already connected (a cycle for edges).
- 4Attach smaller under larger
parent[smallRoot] = bigRoot; size[bigRoot] += size[smallRoot]; components--.
- 5connected(a, b)
find(a) === find(b).
684. Redundant Connection
edges = [[1,2], [1,3], [2,3]]
| Edge | find(u) | find(v) | Same root? | Action |
|---|---|---|---|---|
| [1, 2] | 1 | 2 | no | union: parent[2] = 1 |
| [1, 3] | 1 | 3 | no | union: parent[3] = 1 |
| [2, 3] | 1 | 1 | yes | redundant edge: return [2, 3] |
NOWEdge: [1, 2] | find(u): 1 | find(v): 2 | Same root?: no | Action: union: parent[2] = 1
When both endpoints already share a root, the edge closes a cycle. Each check is nearly O(1), so processing all edges is almost linear.
Implementation
class UnionFind { constructor(n) { this.parent = Array.from({ length: n }, (_, i) => i); this.size = new Array(n).fill(1); this.components = n; } find(x) { while (this.parent[x] !== x) { this.parent[x] = this.parent[this.parent[x]]; // path halving x = this.parent[x]; } return x; } union(a, b) { let ra = this.find(a), rb = this.find(b); if (ra === rb) return false; // already connected if (this.size[ra] < this.size[rb]) [ra, rb] = [rb, ra]; this.parent[rb] = ra; this.size[ra] += this.size[rb]; this.components--; return true; } connected(a, b) { return this.find(a) === this.find(b); }} function findRedundantConnection(edges) { const uf = new UnionFind(edges.length + 1); for (const [u, v] of edges) if (!uf.union(u, v)) return [u, v]; return [];} // 990. Satisfiability of Equality Equationsfunction equationsPossible(equations) { const uf = new UnionFind(26); const id = (ch) => ch.charCodeAt(0) - 97; for (const e of equations) if (e[1] === "=") uf.union(id(e[0]), id(e[3])); return equations.every((e) => e[1] === "=" || !uf.connected(id(e[0]), id(e[3])));}Complexity and performance
Nearly constant with both optimizations.
Trees can become chains.
Effectively linear.
parent and rank arrays.
Trade-offs
For a static graph, BFS or DFS counts components just as well. Union Find wins when edges arrive incrementally and queries are interleaved.
Standard DSU cannot split groups. If edges are removed, process operations in reverse (adding edges) or use other structures.
Variants and related techniques
Store a ratio or offset to the parent to answer equations like a / b = 2 (Evaluate Division).
Skip path compression and keep a stack of changes so unions can be undone, used in offline algorithms.
Use a Map for parent when elements are strings or sparse large numbers.
Common mistakes
- Comparing parent[a] === parent[b] instead of roots.
Fix: Always compare find(a) and find(b).
- Forgetting path compression.
Fix: Without it, chains make find O(n) and solutions time out.
- Off-by-one for 1-indexed nodes.
Fix: Allocate n + 1 slots.
Interview questions
What do path compression and union by rank achieve?
Path compression flattens trees during find; union by rank keeps trees shallow during union. Together they make each operation run in inverse-Ackermann time, which is at most about 4 in practice.
How do you detect a cycle in an undirected graph with Union Find?
Process edges one by one. If the two endpoints already have the same root, adding the edge creates a cycle.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| 547. Number of Provinces | Medium | Component counting. |
| 684. Redundant Connection | Medium | Cycle on union. |
| 990. Satisfiability of Equality Equations | Medium | Union then check. |
| 721. Accounts Merge | Medium | Grouping by shared keys. |
| 1202. Smallest String With Swaps | Medium | Sort within components. |
| 1697. Checking Existence of Edge Length Limited Paths | Hard | Offline sorted queries. |