Skip to content

323. Number of Connected Components in an Undirected Graph (Medium)

Problem

Given n nodes labeled 0 to n - 1 and a list of undirected edges, return the number of connected components.

Example

  • n = 5, edges = [[0,1],[1,2],[3,4]]2
  • n = 5, edges = [[0,1],[1,2],[2,3],[3,4]]1

LeetCode 323 (premium) · Link · Medium

Try it yourself

idle

Starter code: this editor begins with intentional TODOs. Fill the function, run the embedded tests, then compare your solution with the worked approaches below.

Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Approach 1: DFS

Walk each unvisited node; each DFS covers one component.

from collections import defaultdict
def count_components(n, edges):
graph = defaultdict(list)
for u, v in edges: # L1: E iterations
graph[u].append(v) # L2: O(1) each
graph[v].append(u) # L3: O(1) each
visited = set()
def dfs(node):
stack = [node] # L4: O(1) init
while stack: # L5: visits each node once
x = stack.pop() # L6: O(1)
if x in visited:
continue
visited.add(x) # L7: O(1)
for nb in graph[x]: # L8: each edge traversed twice total
if nb not in visited:
stack.append(nb) # L9: O(1)
count = 0
for i in range(n): # L10: V iterations
if i not in visited:
count += 1
dfs(i) # L11: O(V + E) total across all calls
return count

Where the time goes, line by line

Variables: V = n (number of nodes), E = len(edges).

LinePer-call costTimes executedContribution
L1-L3 (build adjacency list)O(1)O(1) per edgeEO(E)O(E)
L10 (outer loop)O(1)O(1)VO(V)O(V)
L5-L9 (DFS stack loop)O(1)O(1) per node/edgeV nodes + 2E edge-visits totalO(V+E)O(V + E) ← dominates

Each node is added to visited exactly once and each edge is pushed onto the stack at most twice (once per direction). The total work across all dfs() calls is O(V+E)O(V + E), not O(V+E)O(V + E) per component.

Complexity

  • Time: O(V+E)O(V + E), driven by L5-L9 summed across all DFS calls.
  • Space: O(V+E)O(V + E) for the adjacency list and visited set; the stack holds at most V entries.

Approach 2: Union-Find (optimal)

Start with n components. Each successful union reduces the count by 1.

def count_components(n, edges):
parent = list(range(n)) # L1: O(V)
count = n # L2: O(1)
def find(x):
while parent[x] != x: # L3: follows path to root
parent[x] = parent[parent[x]] # L4: path halving
x = parent[x]
return x
for u, v in edges: # L5: E iterations
ru, rv = find(u), find(v) # L6: near-O(1) amortized per find
if ru != rv:
parent[ru] = rv # L7: O(1) union
count -= 1 # L8: O(1)
return count

Where the time goes, line by line

Variables: V = n (number of nodes), E = len(edges).

LinePer-call costTimes executedContribution
L1 (init parent array)O(V)O(V)1O(V)O(V)
L5-L8 (edge processing loop)O(alpha(V)O(alpha(V)) per edgeEO(Ealpha(V)O(E * alpha(V)) ← dominates

Complexity

  • Time: O(V+Ealpha(V)O(V + E * alpha(V)), driven by L5-L8. Effectively O(V+E)O(V + E) in practice.
  • Space: O(V)O(V) for the parent array; no adjacency list needed.

Try this approach:

idle
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Summary

ApproachTimeSpace
DFSO(V+E)O(V + E)O(V+E)O(V + E)
BFSO(V+E)O(V + E)O(V+E)O(V + E)
Union-FindO(V+Ealpha(V)O(V + E · alpha(V))O(V)O(V)

All optimal. Pick union-find when edges arrive online or you also need “are u and v in the same component?” queries.

Test cases

# Quick smoke tests, paste into a REPL or save as test_323.py and run.
# Uses the canonical implementation (Approach 2: Union-Find).
def count_components(n, edges):
parent = list(range(n))
count = n
def find(x):
while parent[x] != x:
parent[x] = parent[parent[x]]
x = parent[x]
return x
for u, v in edges:
ru, rv = find(u), find(v)
if ru != rv:
parent[ru] = rv
count -= 1
return count
def _run_tests():
# Canonical example: two components
assert count_components(5, [[0, 1], [1, 2], [3, 4]]) == 2
# Single component spanning all nodes
assert count_components(5, [[0, 1], [1, 2], [2, 3], [3, 4]]) == 1
# No edges: every node is its own component
assert count_components(4, []) == 4
# Single node, no edges
assert count_components(1, []) == 1
# All nodes fully connected (complete graph on 3)
assert count_components(3, [[0, 1], [1, 2], [0, 2]]) == 1
print("all tests pass")
if __name__ == "__main__":
_run_tests()
  • Graphs, connected components via any of the three
  • Union Find, the component tracking structure for connectivity as edges are processed.
  • Graph Traversal, the visited set model for exploring nodes and edges without repetition.