Skip to content

547. Number of Provinces (Medium)

Problem

There are n cities. isConnected[i][j] == 1 if city i and city j are directly connected, else 0. A province is a group of directly or indirectly connected cities. Return the total number of provinces.

Example

  • isConnected = [[1,1,0],[1,1,0],[0,0,1]]2
  • isConnected = [[1,0,0],[0,1,0],[0,0,1]]3

LeetCode 547 · Link · Medium

Try it yourself

idle

Starter code: this editor begins with intentional TODOs. Fill the function, run the embedded tests, then compare your solution with the worked approaches below.

Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Approach 1: DFS, count connected components

Mark each city as visited when first reached. Each unvisited city that triggers a new DFS is a new province.

def find_circle_num_dfs(isConnected):
n = len(isConnected) # L1: number of cities
visited = [False] * n # L2: O(n) visited array
provinces = 0 # L3: O(1)
def dfs(city):
visited[city] = True # L4: O(1) mark visited
for neighbor in range(n): # L5: scan all potential neighbors
if isConnected[city][neighbor] == 1 and not visited[neighbor]:
dfs(neighbor) # L6: O(1) per call, recurse
for city in range(n): # L7: outer scan
if not visited[city]:
provinces += 1 # L8: O(1) new province
dfs(city) # L9: DFS from unvisited city
return provinces

Where the time goes, line by line

Variables: n = number of cities.

LinePer-call costTimes executedContribution
L2 (init visited)O(1)O(1) per citynO(n)O(n)
L7 (outer scan)O(1)O(1)nO(n)O(n)
L5 (neighbor scan inside DFS)O(n)O(n)once per city visitedO(n2)O(n^2) ← dominates
L6 (recurse)O(1)O(1)at most n totalO(n)O(n)

The DFS for each city scans its entire row in isConnected (L5). Every city is visited at most once (L4 prevents re-entry), so L5 executes at most n times for a total of n * n = n^2 operations. This is unavoidable: we must read every entry in the n * n matrix.

Complexity

  • Time: O(n2)O(n^2), driven by L5 (reading the full adjacency matrix).
  • Space: O(n)O(n) visited array plus O(n)O(n) recursion stack.

Approach 2: Union-Find

Union the pairs of directly connected cities. Count distinct root nodes at the end.

def find_circle_num_uf(isConnected):
n = len(isConnected) # L1: number of cities
parent = list(range(n)) # L2: O(n) init, each city is its own root
def find(x):
while parent[x] != x:
parent[x] = parent[parent[x]] # L3: path halving
x = parent[x]
return x
def union(a, b):
ra, rb = find(a), find(b) # L4: O(alpha) each
if ra != rb:
parent[ra] = rb # L5: O(1) link roots
for i in range(n): # L6: scan upper triangle
for j in range(i + 1, n):
if isConnected[i][j] == 1:
union(i, j) # L7: O(alpha) union
return sum(1 for i in range(n) if find(i) == i) # L8: count distinct roots

Complexity

  • Time: O(n2alpha(n)O(n^2 * alpha(n)), effectively O(n2)O(n^2), driven by L6/L7.
  • Space: O(n)O(n) parent array.

Try this approach:

idle
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Summary

ApproachTimeSpaceNotes
DFSO(n2)O(n^2)O(n)O(n)Simple, canonical
Union-FindO(n2alpha)O(n^2 * alpha)O(n)O(n)Useful when adding connections online

Both approaches are bounded by O(n2)O(n^2) because you must read the adjacency matrix. Use DFS/BFS for a one-shot query; use Union-Find when connections are added incrementally.

Test cases

def find_circle_num(isConnected):
n = len(isConnected)
visited = [False] * n
provinces = 0
def dfs(city):
visited[city] = True
for neighbor in range(n):
if isConnected[city][neighbor] == 1 and not visited[neighbor]:
dfs(neighbor)
for city in range(n):
if not visited[city]:
provinces += 1
dfs(city)
return provinces
def _run_tests():
# Example 1: two provinces
assert find_circle_num([[1,1,0],[1,1,0],[0,0,1]]) == 2
# Example 2: three isolated cities
assert find_circle_num([[1,0,0],[0,1,0],[0,0,1]]) == 3
# All connected: one province
assert find_circle_num([[1,1,1],[1,1,1],[1,1,1]]) == 1
# Single city
assert find_circle_num([[1]]) == 1
# Chain: 1-2-3 all one province
assert find_circle_num([[1,1,0],[1,1,1],[0,1,1]]) == 1
print("all tests pass")
if __name__ == "__main__":
_run_tests()
  • Graph Traversal, the visited set model for exploring nodes and edges without repetition.
  • Union Find, the component tracking structure for connectivity as edges are processed.