There are n cities. Some of them are connected, while some are not. If city a is connected directly with city b, and city b is connected directly with city c, then city a is connected indirectly with city c.
A province is a group of directly or indirectly connected cities and no other cities outside of the group.
You are given an n x n matrix isConnected where isConnected[i][j] = 1 if the ith city and the jth city are directly connected, and isConnected[i][j] = 0 otherwise.
Return the total number of provinces.
Example 1:
Input: isConnected = [[1,1,0],[1,1,0],[0,0,1]] Output: 2
Example 2:
Input: isConnected = [[1,0,0],[0,1,0],[0,0,1]] Output: 3
Constraints:
1 <= n <= 200n == isConnected.lengthn == isConnected[i].lengthisConnected[i][j]is1or0.isConnected[i][i] == 1isConnected[i][j] == isConnected[j][i]
Approach: Union-Find (Disjoint Set)
Algorithm
- Initialize each city as its own set with a
parentarray - Iterate over the upper triangle of the matrix; when
isConnected[i][j] == 1, union setsiandj - Count the number of distinct roots among all cities
Time & Space Complexity
- Time Complexity: O(n² * α(n)) - α is the inverse Ackermann function (nearly constant with path compression)
- Space Complexity: O(n) - the parent and rank arrays
Java Implementation
public class FriendCircles {
private int[] parent;
private int[] rank;
/**
* Return the total number of provinces (friend circles).
* @param isConnected n x n adjacency matrix
* @return Number of connected components
*/
public int findCircleNum(int[][] isConnected) {
int n = isConnected.length;
parent = new int[n];
rank = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
}
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (isConnected[i][j] == 1) {
union(i, j);
}
}
}
int count = 0;
for (int i = 0; i < n; i++) {
if (find(i) == i) {
count++;
}
}
return count;
}
private int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
private void union(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX == rootY) {
return;
}
if (rank[rootX] < rank[rootY]) {
parent[rootX] = rootY;
} else if (rank[rootX] > rank[rootY]) {
parent[rootY] = rootX;
} else {
parent[rootY] = rootX;
rank[rootX]++;
}
}
// Test method
public static void main(String[] args) {
FriendCircles fc = new FriendCircles();
int[][] isConnected1 = {{1, 1, 0}, {1, 1, 0}, {0, 0, 1}};
System.out.println("Input: isConnected = [[1,1,0],[1,1,0],[0,0,1]]");
System.out.println("Output: " + fc.findCircleNum(isConnected1)); // Expected: 2
int[][] isConnected2 = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
System.out.println("Input: isConnected = [[1,0,0],[0,1,0],[0,0,1]]");
System.out.println("Output: " + fc.findCircleNum(isConnected2)); // Expected: 3
}
}
Example Walkthrough
For isConnected = [[1,1,0],[1,1,0],[0,0,1]]:
- Union(0,1) because isConnected[0][1] == 1. Cities 0 and 1 are now in the same set
- No other pairs are connected
- Roots: city 0 (representing {0,1}) and city 2 -> 2 provinces
Key Points
- Union-Find: Models connectivity by merging sets; ideal for counting connected components
- Path Compression:
findflattens the tree, making subsequent lookups nearly O(1) - Union by Rank: Attaches the shorter tree under the taller one to keep trees shallow
- Upper Triangle Only: The matrix is symmetric, so checking
j > ihalves the work - Root Count: A node is a root when
parent[i] == i, giving the province count