Algorithm

Number of Provinces

Graphs Pattern

Number of Provinces

You are given an n x n matrix isConnected, where isConnected[i][j] is 1 if city i is directly connected to city j and 0 otherwise.

A province is a maximal group of cities such that every city in the group is reachable from every other, directly or through intermediate cities. Return the number of provinces.

The matrix is symmetric and its diagonal is all 1s, since every city is trivially connected to itself. A city with no connections to any other forms a province of its own.

CONSTRAINTS
  • 1 <= n <= 200
  • isConnected[i][j] is 0 or 1
  • isConnected[i][i] == 1 for every i
  • isConnected[i][j] == isConnected[j][i] — the relation is symmetric
  • Connections are transitive: reachability through intermediates counts
EXAMPLE 1
Input: isConnected = [[1,1,0],[1,1,0],[0,0,1]]
Output: 2
Cities 0 and 1 are directly connected, forming one province. City 2 connects to nothing but itself, forming a second. The diagonal 1s are self-connections and never join two different cities.
EXAMPLE 2
Input: isConnected = [[1,0,0],[0,1,0],[0,0,1]]
Output: 3
Only the diagonal is set, so no two distinct cities are connected. Each city is isolated and forms its own province — n cities with no roads means n provinces.
EXAMPLE 3
Input: isConnected = [[1,1,0],[1,1,1],[0,1,1]]
Output: 1
Cities 0 and 2 have no direct connection, but both connect to city 1, so all three are mutually reachable through it. Provinces are about reachability, not direct adjacency — one indirect link merges two groups.
EXAMPLE 4
Input: isConnected = [[1]]
Output: 1
A single city forms a single province. The smallest input still yields 1, not 0 — worth checking, since an implementation that counts merges rather than groups can get this wrong.
Is the matrix guaranteed symmetric?
Yes, so the relation is undirected and you only need to examine one triangle. Scanning j from i+1 upward halves the work and avoids double-counting each connection.
What do the 1s on the diagonal mean?
Each city is connected to itself, which carries no information. If you scan the full matrix rather than the upper triangle, be careful not to treat isConnected[i][i] as a link that merges anything — it would not change the answer, but it wastes work and confuses traces.
Does a city with no connections count as a province?
Yes, a province of size one. Returning n for a matrix with no off-diagonal 1s is the correct behaviour, not a degenerate case to special-case away.
Would an adjacency list be faster than this matrix?
Yes if the graph were sparse, but the input format forces O(n²) regardless — simply reading the matrix requires touching every entry. With n capped at 200 that is 40,000 cells, which is trivial. This is a good case for noting that the input representation, not the algorithm, sets the lower bound.

The word "province" is decoration. Cities are vertices, connections are undirected edges, and a province is a connected component — a maximal group of mutually reachable vertices. The task is to count them, which is one of the most standard operations in graph work, and there are two clean ways to do it that are worth knowing side by side.

Reading the input for what it is

The matrix is an adjacency matrix: entry (i, j) records whether an edge joins city i and city j. Two properties are given and both matter.

It is symmetric, so entry (i, j) always equals entry (j, i). That confirms the graph is undirected and means you only ever need to look at one triangle of the matrix — scanning j from i+1 upward visits each pair exactly once.

The diagonal is all 1s, meaning each city is connected to itself. That is a formality carrying no information, and it never merges two distinct cities.

One consequence to notice before choosing an algorithm: finding the neighbours of city i means scanning an entire row of n entries, whether that city has 100 connections or none. So any approach costs at least O(n²) simply to read the input. The algorithm cannot be faster than the format.

The first approach: launch one traversal per province

Walk through the cities in order. When you find one not yet visited, you have found a province nobody has counted — so add one to the total, and then traverse that entire province, marking every city in it as visited. When the outer loop later reaches those cities, they are already marked and are skipped.

The two loops have completely separate jobs, and keeping that straight is the key to the pattern. The outer loop finds a starting point in an as-yet-undiscovered province and does the counting. The inner traversal consumes exactly one province so that it can never be counted twice.

python
def find_circle_num(is_connected):
    n = len(is_connected)
    visited = [False] * n

    def visit(city):
        visited[city] = True
        for other in range(n):
            if is_connected[city][other] == 1 and not visited[other]:
                visit(other)

    provinces = 0
    for city in range(n):
        if not visited[city]:
            provinces += 1          # a province no traversal has reached
            visit(city)             # consume all of it
    return provinces
Crucial Notethe increment belongs in the outer loop, not inside visit. Counting inside the traversal would count cities, not provinces. The number you want is how many times you had to start over.
Crucial Notemark visited[city] = True on entry, before recursing. Postpone it and two connected cities call each other endlessly, since the symmetric matrix means each is in the other's row.

Cost: the traversals together enter each city once, and each entry scans a full row of n entries, so the total is O(n²) time with O(n) space. Note the inner loop runs over every column even when almost all are zeros — that is the matrix format, not a flaw in the algorithm. Breadth-first search with a queue would visit exactly the same cities at the same cost; there is no distance in this question, so the traversal order is irrelevant and depth-first is simply shorter.

The second approach: merge groups as you read

The other route never traverses anything. Start by assuming the worst — n cities, n separate provinces — and then merge.

Scan the upper triangle of the matrix. For each connection found, attempt to merge the two cities' groups. If they were already in the same group the connection tells you nothing new. If they were in different groups, two provinces just became one, so decrement the count.

This uses Union-Find, which maintains for each element a pointer toward a representative of its group. Two cities are in the same province exactly when they share a representative, and merging two groups costs a single pointer write.

python
def find_circle_num(is_connected):
    n = len(is_connected)
    parent = list(range(n))
    rank = [0] * n
    provinces = n                             # start pessimistic: all separate

    def find(x):
        while parent[x] != x:
            parent[x] = parent[parent[x]]     # flatten while walking
            x = parent[x]
        return x

    for i in range(n):
        for j in range(i + 1, n):             # upper triangle only
            if is_connected[i][j] == 1:
                ri, rj = find(i), find(j)
                if ri != rj:                  # a genuine merge
                    if rank[ri] < rank[rj]:
                        ri, rj = rj, ri
                    parent[rj] = ri
                    if rank[ri] == rank[rj]:
                        rank[ri] += 1
                    provinces -= 1
    return provinces
Crucial Notedecrement only when the roots actually differ. Decrementing on every 1 in the matrix would subtract once per edge rather than once per merge, and a province containing a cycle would drive the count below zero. The whole method rests on the fact that merging n groups into one takes exactly n-1 successful merges.
Crucial Notescanning j from i + 1 skips the diagonal and each pair's mirror image. Scanning the full matrix still gives the right answer — the redundant merges simply fail — but it doubles the work and makes traces confusing.

Cost: O(n² · α(n)), effectively O(n²), with O(n) space.

Worked example

Take isConnected = [[1,1,0],[1,1,1],[0,1,1]] — city 0 joined to 1, city 1 joined to 2, and 0 not joined to 2 directly.

By traversal. City 0 is unvisited, so provinces becomes 1 and visit(0) runs. It marks 0 and scans row 0, finding a 1 at column 1, so it recurses into city 1. That marks 1 and scans row 1, finding 1s at columns 0 — already visited, skip — and 2, so it recurses into city 2. City 2 marks itself and scans row 2, finding a 1 at column 1, already visited. Everything unwinds. The outer loop then reaches cities 1 and 2, both already marked, and skips them. Answer 1.

By merging. Start with provinces = 3 and each city its own root.
- Pair (0,1) is connected. Roots 0 and 1 differ, so merge and provinces drops to 2.
- Pair (0,2) is not connected. Nothing happens.
- Pair (1,2) is connected. find(1) returns the merged root, find(2) returns 2, they differ, so merge and provinces drops to 1.

Answer 1. Note that cities 0 and 2 were never compared directly and are still correctly in one province — transitivity is handled by the group structure rather than by any explicit check.

Now the isolated case [[1,0,0],[0,1,0],[0,0,1]]. The traversal launches three times, each consuming a single city, giving 3. The merge version finds no off-diagonal 1s, performs no merges, and returns the initial 3.

Choosing between them

Both are optimal here, so the choice is about what the surrounding problem looks like.

Traversal is the better default when the graph is given as a matrix or adjacency list and asked about once. It is shorter, needs no auxiliary structure, and extends naturally when you want more than a count — the size of each province, the members of each, the largest one.

Union-Find wins when connections arrive over time and the count must be reported after each addition. A traversal would have to rerun from scratch after every new road; the merge version updates in effectively constant time. It is also the natural fit when the input is an edge list rather than a matrix.

The recognition signal for both is broad and common: counting connected components. It appears as provinces, friend circles, islands in a grid, groups of accounts to merge, clusters of related items. The underlying shape is always the same — find something not yet claimed, claim everything reachable from it, and count how many times you had to begin again.

Train the reflex: when asked how many separate groups exist, do not try to reason about the groups. Consume one at a time and count the restarts — or start with everything separate and count the merges.

Interactive Strategy Visualization

Number of Provinces

Adjacency Matrix
Connected Groups
1
1
0
0
1
1
0
0
0
0
1
1
0
0
1
1
Adjacency Matrix
PROVINCE 1
0
PROVINCE 2
1
PROVINCE 3
2
PROVINCE 4
3

Phase 1: The Input Matrix

The input is an N x N matrix. A '1' at (i, j) means city i and city j are connected.

O(n³) Reachability Test Per Pair
O(n²) Traversal Per Unvisited City
O(n² · α(n)) Union-Find Merging