Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
What is Union Find?
Union Find (Disjoint Set Union) tracks elements partitioned into disjoint sets. It supports near O(1) operations for finding which set an element belongs to and merging sets.Pattern Recognition Checklist
Use Union Find When
- Counting connected components
- Checking if two nodes are connected
- Cycle detection in undirected graphs
- Dynamic connectivity queries
- Kruskal’s MST algorithm
- Grouping elements by some relation
Don't Use When
- Need shortest path (use BFS/Dijkstra)
- Directed graph cycle detection (use DFS)
- Need to traverse paths
- Static graph (BFS/DFS might be simpler)
Complexity Reference
| Operation | Without Optimization | With Path Compression + Union by Rank |
|---|---|---|
| Find | O(n) | O(alpha(n)) nearly O(1) |
| Union | O(n) | O(alpha(n)) nearly O(1) |
| Connected | O(n) | O(alpha(n)) nearly O(1) |
When to Use
Connected Components
Cycle Detection
Dynamic Connectivity
MST Algorithms
Implementation
Pattern Variations
1. Number of Islands (Union Find approach)
2. Accounts Merge
3. Redundant Connection (Cycle Detection)
Classic Problems
| Problem | Pattern | Key Insight |
|---|---|---|
| Number of Islands | Grid connectivity | 2D to 1D mapping |
| Accounts Merge | Email grouping | Union emails, group by root |
| Redundant Connection | Cycle detection | Edge creating cycle |
| Longest Consecutive | Value connectivity | Union consecutive numbers |
| Kruskal’s MST | Edge processing | Union in weight order |
Interview Problems by Company
- Medium
- Hard
| Problem | Company | Key Concept |
|---|---|---|
| Number of Provinces | All FAANG | Basic connectivity |
| Redundant Connection | Google, Amazon | Cycle detection |
| Accounts Merge | Meta, Amazon | Email grouping |
| Satisfiability | Equality constraints |
Interview Tips
How to Explain Your Approach
How to Explain Your Approach
- “I’ll use Union Find for efficient connectivity queries.”
- “I’ll implement with path compression and union by rank.”
- “Find operation compresses the path to root.”
- “Union attaches smaller tree under larger.”
- “Both operations are nearly O(1) amortized.”
Union Find Template
Union Find Template
Common Variations
Common Variations
| Variation | Use Case |
|---|---|
| Track component size | Largest component problems |
| Weighted Union Find | Equations, ratios |
| 2D to 1D mapping | Grid problems: i * cols + j |
| Virtual nodes | Connect to boundary/source |
Why Path Compression + Union-by-Rank Together?
Each optimization alone is good but not great. Together they hit the theoretical optimum.- Path compression alone -> amortized O(log n) per operation. After many
findcalls, the tree flattens, but a single bad union sequence can rebuild a deep tree. - Union by rank alone -> worst-case O(log n) per operation. The rank rule keeps tree height at most log n, but every find walks the full path.
- Both together -> amortized O(alpha(n)) per operation. Tarjan’s 1975 result.
alpha(n)is the inverse Ackermann function — for any input size you will ever encounter (up to 2^65536),alpha(n) <= 4. Effectively constant.
find, its future find calls are O(1). The two effects compound: rank keeps trees shallow; compression amortizes the cost of any remaining deep paths over future queries. Use one without the other and you give up one of these guarantees.
In interviews, when you implement UF, always include BOTH. Skipping one is a red flag — interviewers know which line was missed.
Worked LeetCode Problems
These five cover every common UF interview pattern: basic component counting (547), undirected cycle detection (684), grouping by shared identifier (721), dynamic / online land additions (305), and minimum-cost connectivity (1319).LC 547 — Number of Provinces (Medium)
Given anisConnected[i][j] adjacency matrix of cities, count the connected groups (provinces). Trivial UF: union every connected pair, return the component count.
LC 684 — Redundant Connection (Medium, undirected cycle detection)
A tree on n nodes has exactly n-1 edges. Add one extra and you create exactly one cycle. Find that extra edge — return the LAST such edge in the input order.LC 721 — Accounts Merge (Medium)
Each account has a name and a list of emails. Two accounts belong to the same person if they share any email. Output merged accounts with sorted emails.LC 305 — Number of Islands II (Hard, online / dynamic UF)
Given a sequence ofaddLand(r, c) operations, return the island count after each operation. Pure DFS / BFS is O(K * m * n) — TLE. UF is O(K * alpha(K)).
LC 1319 — Number of Operations to Make Network Connected (Medium)
You have n computers and a list of cables. Find the minimum number of cable rearrangements to make all n computers connected, or -1 if impossible.len(connections) >= n-1, the answer is (component count - 1).
Caveats and Traps
Curated LeetCode List
| Difficulty | Problem | LC # | Pattern |
|---|---|---|---|
| Easy | Number of Provinces | 547 | Basic UF / connected components |
| Easy | Find if Path Exists in Graph | 1971 | UF or DFS for connectivity |
| Easy | Find the Town Judge | 997 | Degree counting (not strictly UF, related) |
| Medium | Redundant Connection | 684 | Cycle detection in undirected graph |
| Medium | Accounts Merge | 721 | UF on string identifiers |
| Medium | Number of Connected Components | 323 | UF or DFS |
| Medium | Graph Valid Tree | 261 | UF: cycle-free + connected + V-1 edges |
| Medium | Smallest String With Swaps | 1202 | UF on indices, sort within groups |
| Medium | Satisfiability of Equality Equations | 990 | UF on equality, validate inequality |
| Medium | Evaluate Division | 399 | Weighted UF (or DFS with ratios) |
| Medium | Connecting Cities With Minimum Cost | 1135 | MST via Kruskal + UF |
| Medium | Min Cost to Connect All Points | 1584 | MST via Kruskal + UF |
| Medium | Number of Operations to Make Network Connected | 1319 | UF, components - 1 |
| Hard | Number of Islands II | 305 | Online / dynamic UF |
| Hard | Swim in Rising Water | 778 | UF + sorted edges (or Dijkstra) |
| Hard | Bricks Falling When Hit | 803 | Reverse-time UF |
| Hard | Most Stones Removed with Same Row or Column | 947 | UF on rows + columns |
| Hard | Couples Holding Hands | 765 | UF on couple indices, count merges |
Union-Find Interview Q&A
Detect redundant edge in a tree (LC 684) — full walkthrough
Detect redundant edge in a tree (LC 684) — full walkthrough
- Recognize: tree on n nodes has exactly n-1 edges. The input has n edges. The extra one creates exactly one cycle.
- Process edges in order. Before unioning, check if endpoints already share a root.
- If yes -> this edge closes the cycle -> return it.
- If no -> perform the union and continue.
- Since the problem guarantees exactly one cycle, the FIRST collision is the answer.
- Iterating edges twice — once to build the graph, once to detect cycle via DFS. Works but slower and more code than the one-pass UF approach.
- Returning the LAST collision when the problem asks for the answer that appears LAST in the input. For LC 684’s input format, the first collision IS the answer; returning the last only matters in problems with multiple cycles.
- Forgetting that nodes are 1-indexed. Allocating
parent = list(range(n))instead oflist(range(n+1))is an off-by-one that crashes on the largest node.
Accounts Merge — full walkthrough (LC 721)
Accounts Merge — full walkthrough (LC 721)
- Recognize: “accounts merge if they share an email” -> connectivity via shared email -> UF.
- The unifying entity is the EMAIL, not the account index. Two accounts may share an email; the email is what links them.
- Use a
parentdict keyed by email string (no need to map to integers). - For each account, union the first email with every other email in that account.
- Group emails by their root, sort each group, prepend the name.
find does a hash lookup. Map every email to an integer ID first, then run UF on integers. Use an array-based parent. The hashing happens once during ID assignment, not on every operation. Memory: ~8 bytes per email ID instead of ~50 bytes per string. This is a 5x memory reduction and roughly 2-3x speedup.- Unioning ACCOUNT INDICES instead of emails. You then have to figure out which accounts share emails BEFORE unioning — which is the original problem you were trying to solve. Circular.
- Forgetting to sort each merged email list. The problem requires lexicographic order; missing this fails ~30% of test cases.
- Using the FIRST email’s name as the account name vs the original first account’s name. The problem wants the name from the FIRST account that contained that email. Multiple
email_to_namewrites overwrite — be careful or the wrong name appears.
Path compression and union by rank — why both?
Path compression and union by rank — why both?
- Each optimization individually gives O(log n) per operation amortized.
- Together they give O(alpha(n)) — effectively constant for all real-world inputs.
- Path compression flattens the tree during
find, making future calls cheap. - Union by rank prevents creation of deep trees during
union. - The two effects compound: rank limits initial depth; compression flattens any remaining depth.
parent[x] = parent[parent[x]] during traversal — every other node points to its grandparent. Full compression rewrites every node to point directly to the root. Both achieve O(alpha(n)) amortized. Halving is faster in practice (no recursion, fewer cache misses) but slightly worse asymptotic constants. For interviews, full compression is the canonical answer; for production hot paths, path splitting / halving can be a measurable win.- Implementing only path compression and claiming O(alpha(n)). The bound requires both. Path compression alone is O(log n) amortized.
- Implementing only union by rank. Gives O(log n) per operation worst case — better than nothing but not the famous result.
- Path compression that only updates the immediate child of root. Misunderstanding the recursion — the line
parent[x] = find(parent[x])updates EVERY node on the path during the recursive walk back.
Practice Roadmap
Day 1: Basic Union Find
- Solve: Number of Provinces, Redundant Connection
- Focus: Implement find with path compression, union by rank
Day 2: Grid Problems
- Solve: Number of Islands (UF approach), Surrounded Regions
- Focus: 2D to 1D coordinate mapping
Day 3: Advanced Applications
- Solve: Accounts Merge, Satisfiability of Equations
- Focus: Grouping with additional data
Interview Questions
Q1: Explain how path compression works in Union Find and why it matters
Q1: Explain how path compression works in Union Find and why it matters
- Path compression is an optimization applied during the
findoperation. When you traverse from a node up to the root, you make every node along that path point directly to the root. This flattens the tree structure over time. - Without it, the tree can degenerate into a linked list (imagine union-ing 1->2->3->…->n in order), making
findO(n). With path compression, repeatedfindcalls amortize down to O(alpha(n)) — the inverse Ackermann function, which is effectively constant (never exceeds 4 for any practically possible input size, even n = 2^65536). - The implementation is elegant — one line of recursion:
if self.parent[x] != x: self.parent[x] = self.find(self.parent[x]). The key insight is that this is not just caching the root — it physically rewires the parent pointer for every node on the path, so future traversals for any of those nodes are O(1). - A subtle point: path compression alone gives O(log n) amortized. You need both path compression and union by rank/size together to achieve O(
alpha(n)). Most candidates miss this distinction.
- What is the difference between path compression, path splitting, and path halving? When would you choose one over the other?
- Path compression makes
findmutate state. What problems does this cause if you need to undo unions (e.g., in a backtracking algorithm)?
Q2: Union by rank vs union by size — what is the difference, and does it matter?
Q2: Union by rank vs union by size — what is the difference, and does it matter?
- Union by rank tracks the upper bound on tree height. When merging, the shorter tree goes under the taller one. Rank only increases when two trees of equal rank merge.
- Union by size tracks the exact number of nodes in each tree. The smaller tree goes under the larger one.
- Both achieve the same asymptotic complexity — O(
alpha(n)) when combined with path compression. The theoretical bound is identical. - The practical difference: union by size is more useful in many interview problems because
size[root]gives you the exact component size at any time. Problems like “largest component after merging” or “count elements in a group” need size tracking anyway, so union by size pulls double duty. - Rank has a subtle advantage: after path compression,
sizevalues of non-root nodes become stale (they are never updated), butrankwas always just an upper bound, so path compression does not violate its semantics. - My default in interviews: use union by size, because the extra information (exact component size) is free and frequently needed. I only switch to rank if the problem specifically does not need sizes and I want a cleaner implementation.
- If you are tracking component sizes, what happens to the size values of non-root nodes after path compression? Does that matter?
- Can you modify the Union Find to efficiently answer “what is the size of the largest component” at any point? What is the time complexity?
Q3: When would you choose Union Find over BFS/DFS for a connectivity problem?
Q3: When would you choose Union Find over BFS/DFS for a connectivity problem?
- Choose Union Find when edges arrive incrementally (online/streaming setting). For example, “process edges one by one and after each edge, report the number of connected components.” BFS/DFS would require rebuilding the graph and re-traversing every time — O(V+E) per query. Union Find handles each edge in O(
alpha(n)). - Choose Union Find when you only need connectivity answers (are A and B connected? how many components?), not path information. If you need the actual shortest path or any specific path, Union Find cannot help — use BFS.
- Choose BFS/DFS when the graph is static and you only query once. Building Union Find over all edges and then querying is the same complexity as a single BFS/DFS traversal, but with higher constant factors and more code.
- Choose BFS/DFS for directed graphs. Union Find inherently models undirected relationships (union is symmetric). Directed cycle detection, topological sort, strongly connected components — all need DFS.
- Real-world example: Kruskal’s MST algorithm processes edges sorted by weight and uses Union Find to check if adding an edge would create a cycle. You could do this with DFS per edge, but that would be O(E * V) total vs O(E *
alpha(V)) with Union Find — a massive difference on a graph with millions of edges.
- You have a social network with 100M users and connections arrive as a real-time stream. You need to answer “are users X and Y in the same network?” with sub-millisecond latency. Which approach do you pick and why?
- What if the problem requires you to remove edges (disconnect nodes)? Can Union Find handle that directly?
Q4: How does cycle detection work with Union Find, and why does it only work for undirected graphs?
Q4: How does cycle detection work with Union Find, and why does it only work for undirected graphs?
- The principle is simple: process edges one by one. Before adding edge (u, v), check if
find(u) == find(v). If they already share a root, adding this edge would create a cycle — both endpoints are already connected through some existing path. - This works for undirected graphs because connectivity is symmetric. If A can reach B, B can reach A.
- It fails for directed graphs because direction matters. Consider three nodes: edge A->B, edge A->C, then edge B->C. After processing the first two edges, B and C might be in the same component (if we ignore direction), but B->C does not create a cycle. Meanwhile, if the edge were C->A instead, that would be a cycle. Union Find cannot distinguish these cases because it does not track edge direction.
- For directed cycle detection, you need DFS with a recursion stack (tracking “currently visiting” vs “fully visited” states), or Kahn’s algorithm (topological sort via in-degree).
- Classic interview problem: Redundant Connection on LeetCode. You are given a tree plus one extra edge. Find the edge that creates the cycle. Process edges in order — the first edge where
unionreturnsfalseis your answer.
- What if there are multiple redundant edges and you need to return the one that appears last in the input? Does the algorithm still work?
- LeetCode has “Redundant Connection II” for directed graphs. How does the approach change, and why can you not simply use Union Find?
Q5: How would you adapt Union Find to solve problems on a 2D grid?
Q5: How would you adapt Union Find to solve problems on a 2D grid?
- The core technique is coordinate flattening: map cell
(r, c)to indexr * cols + c. This converts a 2D grid into a 1D array that Union Find can index into. - For a grid with
rowsrows andcolscolumns, you initializeUnionFind(rows * cols). - When iterating, you only need to union with right and down neighbors
(r, c+1)and(r+1, c)— not all four directions. Since we process left-to-right, top-to-bottom, the left and up neighbors were already handled when we processed those cells. This halves the number of union operations. - A common gotcha: handling water cells (0s) in an island problem. You must not union water cells. One clean approach: track a separate
landCountvariable, increment it when you see land, decrement it when a union succeeds (merging two land components). - Advanced variant — virtual nodes: for problems like “Surrounded Regions,” create a virtual node that represents the boundary. Union all border O-cells with this virtual node. Then any O-cell connected to the virtual node is not surrounded. This avoids BFS from every border cell.
r + c or r * rows + c as the index formula (wrong formulas that cause collisions), or iterating all four neighbors without realizing the redundancy.Follow-ups:- In the Number of Islands problem, what are the trade-offs between a Union Find approach vs a DFS flood-fill approach? When would you prefer one over the other?
- How would you handle a “Number of Islands II” problem where land cells are added one at a time and you need the component count after each addition?
Q6: What is a weighted Union Find and when would you use it?
Q6: What is a weighted Union Find and when would you use it?
- A weighted Union Find stores an additional value on each edge that represents the relationship between a node and its parent — typically a ratio or offset.
- Classic use case: the “Evaluate Division” problem. You are given equations like a/b = 2.0, b/c = 3.0, and must answer queries like a/c = ?. Each node stores a weight representing its ratio to its parent. When you
find(a), you multiply weights along the path to get the ratio fromato the root. - During path compression, you must update the weight: the new weight from a node to the root is the product of weights along the original path. During union, you compute the correct weight for the new edge by using the relationship:
weight[rootX] = weight[y] * (value of y/x) / weight[x]. - Another use case: “Is Graph Bipartite?” can be modeled as a weighted Union Find where the weight tracks parity (0 or 1 for same-side vs opposite-side).
- The tricky part is getting the math right during
findandunion. Off-by-one errors in the weight propagation are the #1 bug. I always draw out a small example (3-4 nodes) on paper before coding.
- Walk me through exactly how you would update weights during path compression for the division problem. What happens when a chain has three nodes a -> b -> c with weights 2.0 and 3.0?
- What is the time complexity of weighted Union Find? Does the weighting change the amortized bounds?
Q7: Can Union Find support an undo or rollback operation? How?
Q7: Can Union Find support an undo or rollback operation? How?
- Standard Union Find with path compression cannot be efficiently rolled back. Path compression destructively rewires parent pointers, and there is no cheap way to undo those mutations.
- The solution is Union Find by rank/size without path compression, combined with a history stack. Before each union, push the old parent and rank values onto a stack. To undo, pop and restore. Each union/undo is O(log n) instead of O(
alpha(n)), but that is usually acceptable. - This pattern is called rollback Union Find or Union Find with undo, and it appears in competitive programming and certain offline algorithms.
- Real use case: offline dynamic connectivity. You have a sequence of edge additions and removals. Using divide-and-conquer on the time axis, you add edges for the relevant time range and roll back when backtracking. This requires rollback Union Find.
- Another use case: constraint satisfaction problems where you speculatively union elements and need to backtrack if a contradiction is found.
- Key insight: you cannot use path compression with rollback. This is a real trade-off — O(log n) per operation instead of O(
alpha(n)), but you gain reversibility.
- What is the total time complexity of the divide-and-conquer approach for offline dynamic connectivity using rollback Union Find?
- Could you use persistent data structures instead of a rollback stack? What are the trade-offs?
Q8: Walk me through solving the Accounts Merge problem with Union Find
Q8: Walk me through solving the Accounts Merge problem with Union Find
- Step 1 — Recognize it as a connectivity problem. If two accounts share any email, they belong to the same person. This is transitive: if account A shares an email with B, and B shares one with C, all three are the same person. That screams Union Find.
- Step 2 — Map emails to integer IDs. Union Find works on integer indices, not strings. Create a
email_to_iddictionary. As you process each account, assign sequential IDs to new emails. - Step 3 — Union emails within each account. For each account, pick the first email as the “anchor” and union every other email in that account with it. This is O(k *
alpha(n)) per account where k is the number of emails. - Step 4 — Group by root. Iterate over all emails, call
findon each, and group emails by their root ID. This gives you the merged clusters. - Step 5 — Build the result. For each cluster, sort the emails, prepend the account name (stored in a separate
email_to_namemap), and add to the result. - Why Union Find over BFS here: both work, but Union Find is cleaner when you think of emails as nodes and “same account” as edges. The BFS approach requires building an explicit adjacency list first, which is more code and conceptually messier.
- What is the time complexity of your solution? Break it down by step.
- If accounts are arriving as a stream (you do not have all accounts upfront), how would your approach change?
Q9: What is the inverse Ackermann function and why does it appear in Union Find analysis?
Q9: What is the inverse Ackermann function and why does it appear in Union Find analysis?
- The inverse Ackermann function
alpha(n) is the inverse of the Ackermann function A(n, n), which grows incomprehensibly fast. The Ackermann function at just A(4, 4) already exceeds the number of atoms in the observable universe. - Because the Ackermann function grows so fast, its inverse grows unbelievably slowly. For any input size you would ever encounter in practice (up to 2^65536),
alpha(n) is at most 4. - It appears in the Union Find analysis because of a clever amortized argument by Tarjan (1975). The proof partitions nodes into groups based on their “log-star rank” and shows that each group contributes at most O(
alpha(n)) work per operation when amortized over a sequence of m operations. - For practical purposes, you should say: “The amortized cost per operation is O(
alpha(n)), which for all practical purposes is O(1). The total cost of m operations on n elements is O(m *alpha(n)).” - Why this matters beyond trivia: it means Union Find is essentially the fastest possible data structure for dynamic connectivity — there is a matching lower bound showing you cannot do better. This was proven by Fredman and Saks in 1989.
- What is the difference between O(
alpha(n)) and O(log* n)? Which optimizations give you which bound? - You mentioned there is a matching lower bound. Does that mean we can never build a faster data structure for the same problem? What assumptions does the lower bound require?
Q10: Design a system that processes friend requests in a social network and answers 'are X and Y in the same friend circle?' in real time
Q10: Design a system that processes friend requests in a social network and answers 'are X and Y in the same friend circle?' in real time