323. Number of Connected Components In An Undirected Graph - Explanation
Description
You have a graph of n nodes. You are given an integer n and an array edges where edges[i] = [aᵢ, bᵢ] indicates that there is an edge between aᵢ and bᵢ in the graph.
Return the number of connected components in the graph.
Example 1:
Input:
n = 5, edges = [[0,1],[1,2],[3,4]]
Output: 2Example 2:
Input:
n = 5, edges = [[0,1],[1,2],[2,3],[3,4]]
Output: 1Constraints:
1 <= n <= 20001 <= edges.length <= 5000edges[i].length == 20 <= aᵢ <= bᵢ < naᵢ != bᵢ- There are no repeated edges.
Topics
Recommended Time & Space Complexity
You should aim for a solution as good or better than O(V + E) time and O(V + E) space, where V is the number vertices and E is the number of edges in the graph.
Hint 1
Assume there are no edges initially, so we have n components, as there are that many nodes given. Now, we should add the given edges between the nodes. Can you think of an algorithm to add the edges between the nodes? Also, after adding an edge, how do you determine the number of components left?
Hint 2
We can use the Union-Find (DSU) algorithm to add the given edges. For simplicity, we use Union-Find by size, where we merge the smaller component into the larger one. The Union-Find algorithm inserts the edge only between two nodes from different components. It does not add the edge if the nodes are from the same component. How do you find the number of components after adding the edges? For example, consider that nodes 0 and 1 are not connected, so there are initially two components. After adding an edge between these nodes, they become part of the same component, leaving us with one component.
Hint 3
We create an object of the DSU and initialize the result variable res = n, which indicates that there are n components initially. We then iterate through the given edges. For each edge, we attempt to connect the nodes using the union function of the DSU. If the union is successful, we decrement res; otherwise, we continue. Finally, we return res as the number of components.
Prerequisites
Before attempting this problem, you should be comfortable with:
- Graph Representation - Building adjacency lists from edge lists for efficient traversal
- Depth First Search (DFS) - Recursively exploring all reachable nodes from a starting point
- Breadth First Search (BFS) - Level-by-level traversal using a queue to visit connected nodes
- Union-Find (Disjoint Set Union) - Efficiently grouping nodes into sets with union and find operations
1. Depth First Search
Intuition
A connected component is a group of nodes where every node is reachable from any other node in that group.
Using DFS:
- If we start DFS from an unvisited node, we will visit all nodes in its connected component
- Every time we start DFS from a new unvisited node, we’ve found one new component
Algorithm
- Build an adjacency list from the edges.
- Maintain a visited array to track visited nodes.
- Initialize
components = 0. - For each node from
0ton-1:- If the node is not visited:
- Run DFS from this node (mark all reachable nodes as visited)
- Increment
componentsby1
- If the node is not visited:
- Return
components.
class Solution:
def countComponents(self, n: int, edges: List[List[int]]) -> int:
adj = [[] for _ in range(n)]
visit = [False] * n
for u, v in edges:
adj[u].append(v)
adj[v].append(u)
def dfs(node):
for nei in adj[node]:
if not visit[nei]:
visit[nei] = True
dfs(nei)
res = 0
for node in range(n):
if not visit[node]:
visit[node] = True
dfs(node)
res += 1
return resTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of vertices and is the number of edges in the graph.
2. Breadth First Search
Intuition
A connected component is a set of nodes where each node can reach the others.
Using BFS:
- Starting BFS from an unvisited node will visit all nodes in that component
- Each time we start BFS from a new unvisited node, we discover one new connected component
Algorithm
- Build an adjacency list from the given edges.
- Create a visited array to mark nodes that are already seen.
- Initialize
components = 0. - For every node from
0ton-1:- If the node is not visited:
- Start BFS from this node
- Mark all reachable nodes as visited
- Increment
componentsby1
- If the node is not visited:
- Return
components.
class Solution:
def countComponents(self, n: int, edges: List[List[int]]) -> int:
adj = [[] for _ in range(n)]
visit = [False] * n
for u, v in edges:
adj[u].append(v)
adj[v].append(u)
def bfs(node):
q = deque([node])
visit[node] = True
while q:
cur = q.popleft()
for nei in adj[cur]:
if not visit[nei]:
visit[nei] = True
q.append(nei)
res = 0
for node in range(n):
if not visit[node]:
bfs(node)
res += 1
return resTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of vertices and is the number of edges in the graph.
3. Disjoint Set Union (Rank | Size)
Intuition
Disjoint Set Union (DSU) groups nodes into connected components efficiently.
- Start by assuming each node is its own component
- When we process an edge
(u, v):- If
uandvare already in the same set, nothing changes - If they are in different sets, we merge them and the number of components decreases by 1
- If
- Using union by rank/size + path compression keeps operations fast
At the end, the number of remaining sets is the number of connected components.
Algorithm
- Initialize DSU with
nnodes, each node as its own parent. - Set
components = n. - For each edge
(u, v):- If
union(u, v)is successful (they were separate):- Decrement
componentsby1
- Decrement
- If
- Return
components.
class DSU:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [1] * n
def find(self, node):
cur = node
while cur != self.parent[cur]:
self.parent[cur] = self.parent[self.parent[cur]]
cur = self.parent[cur]
return cur
def union(self, u, v):
pu = self.find(u)
pv = self.find(v)
if pu == pv:
return False
if self.rank[pv] > self.rank[pu]:
pu, pv = pv, pu
self.parent[pv] = pu
self.rank[pu] += self.rank[pv]
return True
class Solution:
def countComponents(self, n: int, edges: List[List[int]]) -> int:
dsu = DSU(n)
res = n
for u, v in edges:
if dsu.union(u, v):
res -= 1
return resTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of vertices and is the number of edges in the graph. is used for amortized complexity.
Common Pitfalls
Forgetting Isolated Nodes
When there are no edges, each node is its own component. Solutions that only iterate through edges will miss nodes with no connections and return 0 instead of n.
# Wrong: misses isolated nodes
for u, v in edges:
# only processes connected nodesBuilding a Directed Graph Instead of Undirected
Edges must be added in both directions. Adding only adj[u].append(v) without adj[v].append(u) causes incomplete traversals and overcounts components.
Not Marking Nodes as Visited Before Exploring
In BFS/DFS, marking a node as visited only after processing (instead of when first discovered) can cause nodes to be added to the queue multiple times, leading to incorrect counts or infinite loops.
Sign in to join the discussion