AlgoMaster Logo

Number of Provinces

Last Updated: March 28, 2026

medium
4 min read

Understanding the Problem

This problem is really about counting connected components in an undirected graph. Each city is a node, and isConnected[i][j] = 1 means there's an edge between node i and node j. A "province" is just another word for a connected component, a maximal set of nodes where you can reach any node from any other node through some path.

The adjacency matrix representation is important here. Unlike an adjacency list where you directly see each node's neighbors, the matrix has n x n entries. For any city i, you find its neighbors by scanning row i and looking for all columns j where isConnected[i][j] = 1 (excluding j = i, since every city is trivially connected to itself).

The core question is: how many separate groups of cities exist? If you start at any unvisited city and explore all reachable cities from there, you've found one complete province. The number of times you need to start a fresh exploration equals the number of provinces.

Key Constraints:

  • 1 <= n <= 200 → With n up to 200, the matrix has at most 40,000 entries. O(n^2) is perfectly fine, and even O(n^3) would pass comfortably.
  • isConnected[i][j] == isConnected[j][i] → The graph is undirected. We don't need to worry about directed edges.
  • isConnected[i][i] == 1 → Every city is connected to itself. This is just a property of the matrix, not a useful edge.

Approach 1: DFS (Depth-First Search)

Intuition

The most natural way to think about this problem: start at a city you haven't visited, and explore as far as you can, visiting every city reachable from it. Once you can't reach any more new cities, you've found one complete province. Then look for the next unvisited city and repeat. The number of times you start a fresh DFS traversal is exactly the number of provinces.

This is the classic connected components algorithm. The visited array prevents us from counting the same city twice, and the DFS ensures we reach every city in the current component before moving on.

Algorithm

  1. Create a visited boolean array of size n, initialized to false.
  2. Initialize provinces = 0.
  3. For each city i from 0 to n - 1:
    • If city i has not been visited, start a DFS from city i and increment provinces.
    • The DFS marks city i as visited, then recursively visits all unvisited neighbors of i.
  4. To find neighbors of city i, scan row i of the matrix: city j is a neighbor if isConnected[i][j] == 1 and j != i.
  5. Return provinces.

Example Walkthrough

1Initialize: visited = [false, false, false], provinces = 0
0
false
i
1
false
2
false
1/6

Code

DFS uses recursion, which risks a stack overflow for deep graphs. Can we solve this iteratively instead?

Approach 2: BFS (Breadth-First Search)

Intuition

BFS is an iterative alternative to DFS that avoids recursion entirely. Instead of diving deep into one path, we use a queue to explore all neighbors of the current city before moving to the next level. The result is identical: we find all cities in the same province, one component at a time.

The advantage over DFS is purely practical. BFS uses an explicit queue instead of the call stack, so we don't risk a stack overflow on deep graphs.

Algorithm

  1. Create a visited boolean array of size n, initialized to false.
  2. Initialize provinces = 0.
  3. For each city i from 0 to n - 1:
    • If city i has not been visited, start a BFS from city i and increment provinces.
    • BFS: add i to a queue, mark it visited. While the queue is not empty, dequeue a city, scan its row for unvisited neighbors, and enqueue them.
  4. Return provinces.

Example Walkthrough

1Initialize: visited = [false, false, false], provinces = 0
0
false
i
1
false
2
false
1/6

Code

Both DFS and BFS require graph traversal. What if we could solve this by incrementally merging connected cities into groups, without any traversal at all?

Approach 3: Union Find

Intuition

Union Find (also called Disjoint Set Union) takes a completely different angle. Instead of traversing the graph to discover components, we process each edge and merge the two cities it connects into the same set. At the end, the number of distinct sets equals the number of provinces.

The idea is simple. Start with each city in its own set (n sets total). For each connection isConnected[i][j] = 1, merge the sets containing city i and city j. Each merge reduces the count of distinct sets by 1 (unless they're already in the same set). After processing all connections, the remaining number of sets is the answer.

Path compression and union by rank are two standard optimizations that keep the operations nearly O(1) amortized.

Algorithm

  1. Initialize a parent array where parent[i] = i (each city is its own root).
  2. Initialize a rank array of all zeros (used for union by rank).
  3. Set provinces = n (initially each city is its own province).
  4. For each pair (i, j) where i < j and isConnected[i][j] == 1:
    • Find the root of city i and city j.
    • If the roots are different, merge them (union) and decrement provinces.
  5. Return provinces.

Example Walkthrough

1Initialize: parent = [0, 1, 2], each city is its own root. provinces = 3
0
0
1
1
2
2
1/5

Code