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ᵢ
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.
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?
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.
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.
Before attempting this problem, you should be comfortable with:
A connected component is a group of nodes where every node is reachable from any other node in that group.
Using DFS:
components = 0.0 to n-1:components by 1components.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 resWhere is the number of vertices and is the number of edges in the graph.
A connected component is a set of nodes where each node can reach the others.
Using BFS:
components = 0.0 to n-1:components by 1components.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 resWhere is the number of vertices and is the number of edges in the graph.
Disjoint Set Union (DSU) groups nodes into connected components efficiently.
(u, v):u and v are already in the same set, nothing changesAt the end, the number of remaining sets is the number of connected components.
n nodes, each node as its own parent.components = n.(u, v):union(u, v) is successful (they were separate):components by 1components.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 resWhere is the number of vertices and is the number of edges in the graph. is used for amortized complexity.
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 nodesEdges must be added in both directions. Adding only adj[u].append(v) without adj[v].append(u) causes incomplete traversals and overcounts components.
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.