Number of Provinces

Medium DFS

Problem

Given an n x n matrix isConnected where isConnected[i][j] = 1 if the ith city and the jth city are directly connected, and 0 otherwise, return the total number of provinces (groups of directly or indirectly connected cities).

  • 1 ≤ n ≤ 200
  • isConnected.length == n
  • isConnected[i].length == n
  • isConnected[i][j] is 0 or 1
  • isConnected[i][i] == 1
  • isConnected[i][j] == isConnected[j][i]

Example

Input: isConnected = [[1,1,0],[1,1,0],[0,0,1]]
Output: 2

The cities 0 and 1 are directly connected, forming one province. City 2 is isolated, forming a second province. The DFS starts at city 0, marks city 1 as visited through the connection, and completes the first province. Then it moves to city 2, which is unvisited, marking the second province. The total count is 2.

Approach

Straightforward Solution

A brute-force approach would check every pair of cities and attempt to group them, but without efficient traversal, this leads to redundant checks and higher complexity.

Core Observation

The problem reduces to counting connected components in an undirected graph represented by an adjacency matrix. Each city is a node, and edges exist where isConnected[i][j] == 1.

Path to Optimal

Recognizing the problem as connected components counting suggests graph traversal algorithms. DFS or BFS can explore all nodes reachable from a starting node, marking them visited. Each unvisited node found during iteration indicates a new province. This approach ensures each city is visited exactly once, avoiding redundant work.

Optimal Approach

Iterate over each city. If it is unvisited, increment the province count and perform a DFS to mark all cities connected to it as visited. The DFS recursively visits all neighbors connected directly or indirectly. After processing all cities, the province count reflects the number of connected components.

Time

O(n^2)

The adjacency matrix has n^2 entries. The DFS explores each node and its neighbors once, resulting in O(n^2) time since each edge is checked via the matrix.

Space

O(n)

The auxiliary space is O(n) for the recursion stack in the worst case and the visited set storing up to n nodes.

Pattern Spotlight

DFS (Connected Components in Graph)

When counting groups of connected nodes in an undirected graph, use DFS to explore and mark all nodes in a component starting from any unvisited node, incrementing the count each time a new unvisited node is found.

Solution

1class Solution:
2 def findCircleNum(self, isConnected: List[List[int]]) -> int:
3 n = len(isConnected)
4 seen = set()
5
6 def dfs(i):
7 for j in range(n):
8 if isConnected[i][j] == 1 and j not in seen:
9 seen.add(j)
10 dfs(j)
11
12 provinces = 0
13 for i in range(n):
14 if i not in seen:
15 provinces += 1
16 seen.add(i)
17 dfs(i)
18
19 return provinces

Step-by-Step Solution

1

Initialize Variables and Define DFS Traversal

3n = len(isConnected)
4seen = set()
6def dfs(i):
7 for j in range(n):
8 if isConnected[i][j] == 1 and j not in seen:
9 seen.add(j)
10 dfs(j)

Objective

To prepare the data structures needed for traversal and define the recursive DFS function to explore connected cities.

Key Insight

Storing visited cities in a set prevents revisiting and infinite loops. The DFS function systematically explores all neighbors of a city, recursively marking connected cities as visited. This encapsulates the core traversal logic, enabling efficient connected component detection.

Interview Quick-Check

Core Logic

The DFS function recursively visits all cities connected to the current city, marking them visited to avoid repeated processing.

State & Boundaries

The visited set tracks which cities have been explored, ensuring each city is processed once.

Common Pitfalls & Bugs

Forgetting to add a city to the visited set before recursive DFS calls can cause infinite recursion or repeated visits.

2

Count Provinces by Iterating Over Cities and Invoking DFS

12provinces = 0
13for i in range(n):
14 if i not in seen:
15 provinces += 1
16 seen.add(i)
17 dfs(i)

Objective

To iterate through all cities, identify unvisited ones, increment the province count, and trigger DFS to mark all connected cities.

Key Insight

Each unvisited city represents a new province. By invoking DFS from that city, all cities in the same province are marked visited, preventing double counting. This approach ensures a linear pass over all cities with efficient grouping.

Interview Quick-Check

Core Logic

Increment the province count only when encountering an unvisited city, then mark its entire connected component via DFS.

State & Boundaries

The loop covers all cities, ensuring no province is missed.

Common Pitfalls & Bugs

Failing to add the starting city to the visited set before DFS can cause revisiting and incorrect province counts.

3

Return the Total Number of Provinces

19return provinces

Objective

To output the final count of provinces after all cities have been processed.

Key Insight

After all DFS traversals, the provinces variable accurately reflects the number of connected components in the graph, which is the problem's required output.

Interview Quick-Check

Core Logic

Returning the province count completes the solution, representing the total connected components found.

Line Analysis

This solution has 3 Critical lines interviewers watch for.

Line 8 Critical
if isConnected[i][j] == 1 and j not in seen:

Check if the neighbor city is connected and unvisited.

This conditional is critical because it ensures the DFS only explores valid, unvisited neighbors, preventing cycles and redundant traversals.

Line 9 Critical
seen.add(j)

Mark the neighbor city as visited.

Marking a city as visited before recursive calls is essential to avoid revisiting the same city multiple times, which would cause infinite recursion or incorrect province counts.

Line 16 Critical
seen.add(i)

Mark the current city as visited before DFS.

Adding the city to visited before DFS is critical to avoid infinite recursion and to correctly identify the province's extent.

Line 4 Core
seen = set()

Initialize a set to track visited cities.

The visited set prevents revisiting cities, avoiding infinite recursion and redundant work during DFS traversal.

Line 10 Core
dfs(j)

Recursively perform DFS on the neighbor city.

This recursive call explores all cities connected to the neighbor, extending the traversal to the entire connected component.

Line 14 Core
if i not in seen:

Check if the current city has not been visited.

An unvisited city indicates a new province, triggering a DFS to mark all connected cities.

Line 17 Core
dfs(i)

Invoke DFS to mark all cities connected to the current city.

This call explores the entire connected component, marking all cities in the province as visited.

Line 6 Core
def dfs(i):

Define the recursive DFS function to explore connected cities.

Encapsulating the traversal logic in a recursive function allows systematic exploration of all cities connected to a given city, marking them visited.

Line 15 Core
provinces += 1

Increment the province count for a new connected component.

Each unvisited city found corresponds to a distinct province, so the count must increase.

Line 19 Core
return provinces

Return the total number of provinces found.

After all cities are processed, this value correctly represents the number of connected components in the graph.

Line 7 Standard
for j in range(n):

Iterate over all possible neighboring cities.

Checking all cities ensures that all direct connections from the current city are considered for traversal.

Line 13 Standard
for i in range(n):

Iterate over each city to identify unvisited provinces.

This loop ensures every city is checked, guaranteeing all provinces are counted.

Line 3 Standard
n = len(isConnected)

Calculate the number of cities from the adjacency matrix size.

Knowing the number of cities (nodes) is essential for iterating over all cities and indexing the adjacency matrix correctly.

Line 12 Standard
provinces = 0

Initialize the province count to zero.

This variable accumulates the total number of connected components found during iteration.

Test Your Understanding

Why does performing a DFS from each unvisited city correctly count the number of provinces?

Because each DFS call explores all cities connected directly or indirectly to the starting city, marking them visited. Thus, each DFS corresponds to exactly one connected component (province), and counting these calls yields the total number of provinces.

Related Problems

DFS pattern

Don't just read it. Drill it.

Reconstruct Number of Provinces from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.

Unlock the Number of Provinces drill

or drill a free problem