Number of Connected Components Solution
Given n nodes and a list of undirected edges, find the number of connected components in the graph.
Problem Statement
Difficulty
Medium
LeetCode
Pattern
Graph — Union-Find / DFS connected components
You have n nodes labeled 0 to n-1 and a list of undirected edges. Return the number of connected components in the graph. A connected component is a maximal set of nodes where every pair is reachable from every other.
DFS — O(V+E)
Build an adjacency list. Iterate through all nodes. For each unvisited node, run a DFS to mark every reachable node as visited and increment the component count. The total count equals the number of connected components.
Each DFS call discovers exactly one connected component — all nodes reachable from the starting node. The outer loop ensures every node is visited. The number of DFS calls from the outer loop equals the number of distinct components.
| Metric | Value |
|---|---|
| Time | O(V + E) — each node and edge visited once |
| Space | O(V + E) — adjacency list + O(V) visited + O(V) call stack |
Union-Find — O(V + E·α(V))
Start with n components (each node is its own component). For each edge [u, v], union their components. If they're already in the same component, do nothing. Otherwise, merge and decrement the component count. The final count is the answer.
n students each standing alone in a schoolyard. Each edge is a "friendship declaration" — when two students become friends, their entire friend groups merge into one. You start with n groups. Each friendship either merges two existing groups (count decreases by 1) or connects two already-in-the-same-group students (count stays the same). After processing all friendships, the number of remaining groups is your answer.
- Initialize:
parent[i] = i,rank[i] = 0,components = n. - For each edge [u, v]: if
find(u) ≠ find(v), union them andcomponents--. - Return
components.
- "How many groups / connected components?" in an undirected graph.
- Union-Find is ideal when edges arrive one by one and you need incremental connectivity.
- It's also the backbone of Kruskal's MST algorithm.
- Related problems: LC 200 (Number of Islands — grid version), LC 684 (Redundant Connection — find first cycle-creating edge), LC 547 (Number of Provinces — adjacency matrix variant).
- Every successful union merges two components into one — reducing the count by exactly 1.
- If all edges connect already-connected nodes, the count stays at n.
- This counting strategy avoids a final pass over
parent[]to count distinct roots (which would also work but is less elegant).
Visual Walkthrough
Trace Union-Find on n=5, edges=[[0,1],[1,2],[3,4]]. Expected: 2 components.
Code — Java & Python
Complexity Analysis
| Approach | Time | Space | Trade-off |
|---|---|---|---|
| DFS / BFS | O(V + E) | O(V + E) | Builds full adjacency list; recursive DFS risks stack overflow for large V |
| Union-Find ← optimal | O(V + E·α(V)) | O(V) | No adjacency list needed; near-constant per operation; handles dynamic edges |
- Both are valid and nearly identical in performance for this problem.
- Choose DFS if you need to enumerate which nodes belong to which component.
- Choose Union-Find if you only need the count, or if edges arrive dynamically (streaming).
- In interviews, Union-Find for this problem signals knowledge of the DSU data structure — a strong signal.
- DFS is simpler to code for someone less familiar with Union-Find.
Edge Cases & Pitfalls
| Case | Input | Why It Matters |
|---|---|---|
| No edges | n=5, edges=[] | Every node is its own component. Return n=5. |
| Fully connected | n=3, edges=[[0,1],[1,2]] | Two unions, each reduces count by 1. 3-2=1. Return 1. |
| Single node | n=1, edges=[] | One component trivially. Return 1. |
| Redundant edge | n=3, edges=[[0,1],[1,2],[0,2]] | Third edge [0,2]: find(0)==find(2) already (both root=0). No decrement. Count stays 1. |
| Many isolated nodes | n=100, edges=[[0,1]] | Only one union. 100-1=99 components. |
| 0-indexed vs 1-indexed | Nodes labeled 0..n-1 | Unlike LC 684 (1-indexed), this problem is 0-indexed. Parent array size = n (not n+1). |
union(e[0], e[1]) directly without first checking if find(e[0]) != find(e[1]) and then decrementing unconditionally. If both nodes are already in the same component, you'd decrement the count when no merge happened — producing a count that's too low. Always check find(u) != find(v) before decrementing, or have union() return a boolean indicating whether a merge occurred.