You are given an undirected graph represented by n
nodes (labeled from 0
to n - 1
) and a list of edges
, where each edge
is a pair of node indices. The goal is to determine the number of connected components in the graph. A connected component is a subgraph in which any two vertices are connected to each other by a path.
Example 1:
n = 5
edges = [[0, 1], [1, 2], [3, 4]]
In this example, there are two connected components: {0, 1, 2}
and {3, 4}
. Therefore, the function should return 2.
Example 2:
n = 5
edges = [[0, 1], [1, 2], [2, 3], [3, 4]]
In this case, all nodes are connected, forming a single connected component. The function should return 1.
Example 3:
n = 6
edges = [[0, 1], [0, 2], [2, 5], [5, 4], [3, 3]]
Here, the connected components are {0, 1, 2, 4, 5}
and {3}
. The self-loop [3, 3]
does not affect the number of components, so the function should return 2.
Constraints:
1 <= n <= 2000
0 <= edges.length <= 5000
edges[i].length == 2
0 <= ai, bi < n
ai != bi
Write a function that takes the number of nodes n
and the list of edges edges
as input and returns the number of connected components in the graph. Discuss the time and space complexity of your solution. Can you think of multiple ways to solve this problem, and what are the trade-offs?
Given an undirected graph with n
nodes and a list of edges
, determine the number of connected components in the graph.
For example, consider a graph with 5 nodes (numbered 0 to 4) and the following edges:
edges = [[0, 1], [1, 2], [3, 4]]
In this case, there are two connected components: {0, 1, 2}
and {3, 4}
. Therefore, the expected output is 2.
A brute-force approach involves iterating through each node in the graph and performing a Depth-First Search (DFS) to explore its connected component. We can maintain a visited
set to keep track of nodes we have already processed. For each unvisited node, we increment the component count and perform DFS to mark all nodes in its connected component as visited.
count = 0
and visited = set()
.edges
.n-1
.visited
:
count
.count
.def count_components_dfs(n, edges):
adj_list = [[] for _ in range(n)]
for u, v in edges:
adj_list[u].append(v)
adj_list[v].append(u)
visited = set()
count = 0
def dfs(node):
visited.add(node)
for neighbor in adj_list[node]:
if neighbor not in visited:
dfs(neighbor)
for i in range(n):
if i not in visited:
count += 1
dfs(i)
return count
visited
set and recursion stack in DFS can take up O(V) space.A more efficient approach to solve this problem is using the Disjoint Set Union (DSU) data structure, also known as Union-Find.
n
nodes, where each node is initially in its own set.(u, v)
in the edges
list.union
operation on u
and v
to merge their sets.def count_components_dsu(n, edges):
parent = list(range(n))
rank = [0] * n
def find(node):
if parent[node] != node:
parent[node] = find(parent[node]) # Path compression
return parent[node]
def union(node1, node2):
root1 = find(node1)
root2 = find(node2)
if root1 != root2:
if rank[root1] < rank[root2]:
parent[root1] = root2
elif rank[root1] > rank[root2]:
parent[root2] = root1
else:
parent[root2] = root1
rank[root1] += 1
return 1 # Successfully unioned
return 0 # Already in the same component
count = n
for u, v in edges:
count -= union(u, v)
return count
parent
and rank
arrays in the DSU data structure.n = 0
, the number of connected components is 0.edges
is empty, the number of connected components is n
(each node is its own component).