Given an undirected graph represented by a number of nodes n
and a list of edges edges
, write a function to find the number of connected components in the graph.
Each node is labeled from 0
to n - 1
. The edges
list contains pairs of node labels representing the connections between them.
A connected component is defined as a subgraph in which any two vertices are connected to each other by a path, and which is connected to no additional vertices in the supergraph.
For example:
n = 5
and edges = [[0, 1], [1, 2], [3, 4]]
, the function should return 2
. The two connected components are {0, 1, 2}
and {3, 4}
.n = 5
and edges = [[0, 1], [1, 2], [2, 3], [3, 4]]
, the function should return 1
. All nodes are connected in a single component.n = 5
and edges = [[0, 1], [2, 3], [4, 4]]
, the function should return 2
. The connected components are {0, 1}
, {2, 3}
, and {4}
(a single node connected to itself).n = 10
and edges = [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]
, the function should return 5
. Each pair forms a connected component.Consider edge cases such as an empty graph (n = 0), a graph with no edges, and a graph with self-loops. Explain the time and space complexity of your approach. Can you propose an algorithm that performs better on large graphs?
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
The goal is to find how many separate groups of friends exist where friends are defined as people directly or indirectly connected. The brute force approach involves exploring all possible groupings by exhaustively checking connections between people. It's like trying every possible team combination to see which teams form independently.
Here's how the algorithm would work step-by-step:
def count_connected_components_brute_force(number_of_nodes, edges):
# Initialize each node as its own component
node_components = list(range(number_of_nodes))
# Iterate through all possible pairs of nodes
for first_node in range(number_of_nodes):
for second_node in range(first_node + 1, number_of_nodes):
# Check if an edge exists between the two nodes.
if (first_node, second_node) in edges or (second_node, first_node) in edges:
# Merge components if nodes are connected
first_node_component = node_components[first_node]
second_node_component = node_components[second_node]
# If they are in different components, merge them
if first_node_component != second_node_component:
# Update all nodes in the first component to the second
for node_index in range(number_of_nodes):
if node_components[node_index] == first_node_component:
node_components[node_index] = second_node_component
# Count the number of distinct components
number_of_components = len(set(node_components))
return number_of_components
The problem asks us to count how many separate groups of friends exist in a network. The key idea is to explore each person's group completely before moving on to the next, using a smart method to keep track of who we've already visited.
Here's how the algorithm would work step-by-step:
def count_connected_components(number_of_nodes, adjacency_list):
visited_nodes = [False] * number_of_nodes
number_of_components = 0
def depth_first_search(node):
visited_nodes[node] = True
for neighbor in adjacency_list[node]:
if not visited_nodes[neighbor]:
depth_first_search(neighbor)
# Iterate through all nodes
for node in range(number_of_nodes):
# Ensure that we only explore unvisited components.
if not visited_nodes[node]:
depth_first_search(node)
number_of_components += 1
# Once the DFS is complete, increment the counter
return number_of_components
Case | How to Handle |
---|---|
Null or empty graph (no nodes) | Return 0 as there are no connected components in an empty graph. |
Graph with only one node | Return 1 as a single node constitutes one connected component. |
Graph with no edges (all nodes isolated) | Return the number of nodes as each node is its own component. |
Graph with a single connected component (fully connected) | Return 1, representing the entire graph as one component. |
Graph represented as adjacency list with self-loops (node connected to itself) | Ensure the DFS or BFS algorithm avoids infinite loops by marking visited nodes. |
Graph with disconnected nodes (multiple components) | The algorithm must correctly identify and count all separate components. |
Large graph (large number of nodes and edges) | Use an efficient algorithm like DFS or BFS with appropriate data structures to avoid stack overflow or excessive memory usage; consider iterative BFS for very large graphs. |
Graph with duplicate edges between same nodes | The algorithm should still correctly identify connected components regardless of duplicate edges as they don't affect connectivity. |