medium Number of Provinces
Problem Statement
There are n cities. Some of them are connected in a network. If City A is directly connected to City B, and City B is directly connected to City C, city A is indirectly connected to City C.
If a group of cities are connected directly or indirectly, they form a province.
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, determine the total number of provinces.
Examples
- Example 1:
- Input: isConnected =
[[1,1,0],[1,1,0],[0,0,1]]
- Input: isConnected =
-
Expected Output:
2 -
Justification: Here, city
1and2form a single provenance, and city3is one provinces itself. -
Example 2:
- Input: isConnected =
[1,0,0],[0,1,0],[0,0,1]]
- Input: isConnected =
-
Expected Output:
3 -
Justification: In this scenario, no cities are connected to each other, so each city forms its own province.
-
Example 3:
- Input: isConnected =
[[1,0,0,1],[0,1,1,0],[0,1,1,0],[1,0,0,1]]
- Input: isConnected =
- Expected Output:
2 - Justification: Cities 1 and 4 form a province, and cities 2 and 3 form another province, resulting in a total of 2 provinces.
Constraints:
1 <= n <= 200n == isConnected.lengthn == isConnected[i].lengthisConnected[i][j]is1or0.isConnected[i][i] == 1isConnected[i][j] == isConnected[j][i]
Try it yourself
Try solving this question here:
✅ Solution Number of Provinces
Problem Statement
There are n cities. Some of them are connected in a network. If City A is directly connected to City B, and City B is directly connected to City C, city A is indirectly connected to City C.
If a group of cities are connected directly or indirectly, they form a province.
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, determine the total number of provinces.
Examples
- Example 1:
- Input: isConnected =
[[1,1,0],[1,1,0],[0,0,1]]
- Input: isConnected =
-
Expected Output:
2 -
Justification: Here, city
1and2form a single provenance, and city3is one province itself. -
Example 2:
- Input: isConnected =
[1,0,0],[0,1,0],[0,0,1]]
- Input: isConnected =
-
Expected Output:
3 -
Justification: In this scenario, no cities are connected to each other, so each city forms its own province.
-
Example 3:
- Input: isConnected =
[[1,0,0,1],[0,1,1,0],[0,1,1,0],[1,0,0,1]]
- Input: isConnected =
- Expected Output:
2 - Justification: Cities 1 and 4 form a province, and cities 2 and 3 form another province, resulting in a total of 2 provinces.
Constraints:
1 <= n <= 200n == isConnected.lengthn == isConnected[i].lengthisConnected[i][j]is1or0.isConnected[i][i] == 1isConnected[i][j] == isConnected[j][i]
Solution
To solve this problem, we'll employ the Union-Find algorithm, a powerful algorithm for managing disjoint sets. The essence of the approach is to group cities into sets, where each set represents a connected province.
Initially, every city is considered its own province. As we go through the matrix, we use Union operations to join cities that are directly connected, effectively merging their sets. The Find operation identifies the representative of each set (or province) and helps in determining if two cities are already in the same province. By iteratively applying Union operations to all connected city pairs, we merge their respective sets.
In the end, the number of distinct sets (or provinces) is determined by counting the unique representatives of each set, providing the solution to our problem. This approach ensures efficient handling of connections and an accurate count of disconnected provinces.
Step-by-Step Algorithm
-
Initialization:
- Create a Union-Find data structure with
parentandrankarrays. - Initialize the
parentarray such that each node is its own parent. - Initialize the
rankarray with zeros.
- Create a Union-Find data structure with
-
Process the Connections:
- For each pair of nodes (i, j) in the adjacency matrix
isConnected:- If
isConnected[i][j]is 1, indicating a direct connection:- Use the
findoperation to check the roots of nodesiandj. - If the roots are different, decrement the
numberOfProvincesand perform theunionoperation to merge the sets containing nodesiandj.
- Use the
- If
- For each pair of nodes (i, j) in the adjacency matrix
-
Return the Result:
- The final value of
numberOfProvincesrepresents the number of connected components or provinces.
- The final value of
Algorithm Walkthrough
Given an input isConnected = [[1,1,0],[1,1,0],[0,0,1]], let's walk through the algorithm:
Initial Setup
-
Initialization:
parent = [0, 1, 2](each node is its own parent)rank = [0, 0, 0](initial ranks are zero)numberOfProvinces = 3(initially, each node is its own province)
-
First Node (i = 0):
- (i = 0, j = 1):
isConnected[0][1] = 1(nodes 0 and 1 are connected)find(0)returns 0 (root of 0)find(1)returns 1 (root of 1)- Roots are different, so decrement
numberOfProvincesto 2. - Perform
union_set(0, 1):find(0)returns 0find(1)returns 1rank[0] == rank[1], so attach root of 1 to root of 0 and incrementrank[0].- Updated
parent = [0, 0, 2]andrank = [1, 0, 0].
- (i = 0, j = 1):
- Second Node (i = 1):
- (i = 1, j = 2):
isConnected[1][2] = 0(nodes 1 and 2 are not connected)- No action needed.
- (i = 1, j = 2):
- Third Node (i = 2):
- No connections to check (as j only goes from i+1 to n).
- The final count of provinces is 2.
Code
class UnionFind {
int[] parent;
int[] rank;
// Initialize Union-Find structure with parent and rank arrays.
public UnionFind(int size) {
parent = new int[size];
rank = new int[size];
// Initially, each element is its own parent (self loop).
for (int i = 0; i < size; i++) {
parent[i] = i;
rank[i] = 0;
}
}
// Find the root representative of the set containing x with path compression.
public int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]); // Path compression.
}
return parent[x];
}
// Union two sets containing x and y using union by rank.
public void union_set(int x, int y) {
int rootX = find(x);
int rootY = find(y);
// If they are in the same set, do nothing.
if (rootX == rootY) {
return;
}
// Union by rank.
if (rank[rootX] < rank[rootY]) {
parent[rootX] = rootY;
} else if (rank[rootX] > rank[rootY]) {
parent[rootY] = rootX;
} else {
parent[rootY] = rootX;
rank[rootX]++;
}
}
}
class Solution {
// Function to find the number of provinces (connected components) in the graph.
public int findProvinces(int[][] isConnected) {
int n = isConnected.length;
UnionFind uf = new UnionFind(n);
int numberOfProvinces = n;
// Iterate over each pair of nodes and union the sets if there is a connection.
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (isConnected[i][j] == 1 && uf.find(i) != uf.find(j)) {
numberOfProvinces--;
uf.union_set(i, j);
}
}
}
return numberOfProvinces;
}
// Main method for testing.
public static void main(String[] args) {
Solution solution = new Solution();
// Example 1:
int[][] example1 = { { 1, 1, 0 }, { 1, 1, 0 }, { 0, 0, 1 } };
System.out.println(solution.findProvinces(example1)); // Expected Output: 2
// Example 2:
int[][] example2 = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } };
System.out.println(solution.findProvinces(example2)); // Expected Output: 3
// Example 3:
int[][] example3 = {
{ 1, 0, 0, 1 },
{ 0, 1, 1, 0 },
{ 0, 1, 1, 0 },
{ 1, 0, 0, 1 },
};
System.out.println(solution.findProvinces(example3)); // Expected Output: 2
}
}
Complexity Analysis
Time Complexity
-
Initialization:
- Initializing the
parentandrankarrays takestime, where (N) is the number of nodes (or cities).
- Initializing the
-
Find Operation:
- The
findoperation with path compression has an amortized time complexity of, where is the inverse Ackermann function. This function is extremely slow-growing and can be considered nearly constant for practical input sizes.
- The
-
Union Operation:
- The
union_setoperation involves twofindoperations and one assignment, leading to a time complexity ofper union.
- The
-
Processing Connections:
- The nested loops iterate through each pair of nodes to check for connections. This involves
iterations, where each iteration involves at most two findoperations and oneunion_setoperation. Thus, the total time complexity for processing all connections is.
- The nested loops iterate through each pair of nodes to check for connections. This involves
Given that
Space Complexity
- Parent and Rank Arrays:
- The
parentandrankarrays requirespace each, where (N) is the number of nodes.
- The
Alternate Approach (Using DFS)
At a high level, the problem of identifying provinces in the given matrix can be visualized as detecting connected components in an undirected graph. Every city represents a node, and a direct connection between two cities is an edge. The number of separate, interconnected clusters in this graph is essentially the number of provinces. To navigate this graph and identify these clusters, we employ the Depth First Search (DFS) technique, marking visited nodes (cities) along the way.
Step-by-step Algorithm
-
Initialization:
- Initialize an integer variable
provincesto0. This will count the number of distinct provinces. - Create a
visitedarray of the same size as the number of cities (isConnected.size()), initialized tofalse. This array keeps track of whether a city has been visited.
- Initialize an integer variable
-
Iterate Over Each City:
- Loop through each city
ifrom0ton-1(wherenis the number of cities). - For each city
i, check if it has been visited. If not, it indicates the start of a new province. - Increment the
provincescounter, as this city marks the start of a new province. - Call the
dfsmethod to explore all cities connected to cityi.
- Loop through each city
-
Depth-First Search (DFS):
- In the
dfsmethod, start with cityi. - Loop through each city
jfrom0ton-1. - If city
iis connected to cityj(isConnected[i][j] == 1) and cityjhas not been visited (!visited[j]), mark cityjas visited (visited[j] = true). - Recursively call the
dfsmethod with cityjas the new starting point to explore all its connected cities.
- In the
-
Repeat DFS for Each Province:
- Once the DFS completes for a city, return to the main loop in the
findCircleNummethod and continue with the next unvisited city. - Repeat this process until all cities have been visited.
- Once the DFS completes for a city, return to the main loop in the
-
Return the Number of Provinces:
- After all cities have been checked, return the value of
provinces, which now holds the total number of distinct provinces.
- After all cities have been checked, return the value of
Algorithm Walkthrough
Let's walk through the algorithm using the example vector<vector<int>> example1 = {{1, 1, 0}, {1, 1, 0}, {0, 0, 1}}.
Matrix Representation:
0 1 2
0 [1, 1, 0]
1 [1, 1, 0]
2 [0, 0, 1]
Here:
- City 0 is connected to City 1.
- City 2 is isolated.
Step 1: Initialization
provinces = 0visited = [false, false, false](Initially, no city is visited)
Step 2: First Iteration (i = 0)
- Check if City 0 has been visited:
visited[0] == false. - Since City 0 has not been visited, initiate DFS starting from City 0.
- Increment the province count:
provinces = 1.
Step 3: DFS for City 0
- Mark City 0 as visited:
visited[0] = true→visited = [true, false, false]. - Check the connection between City 0 and other cities:
- City 0 to City 0: Already visited or same city, so skip.
- City 0 to City 1:
isConnected[0][1] == 1andvisited[1] == false.- Mark City 1 as visited:
visited[1] = true→visited = [true, true, false]. - Perform DFS for City 1.
- Mark City 1 as visited:
Step 4: DFS for City 1
- Check the connection between City 1 and other cities:
- City 1 to City 0: Already visited, so skip.
- City 1 to City 1: Already visited or same city, so skip.
- City 1 to City 2:
isConnected[1][2] == 0, so skip.
- DFS completes for City 1, backtrack to complete DFS for City 0.
Step 5: Complete DFS for City 0
- All connected cities for City 0 are visited. DFS for City 0 is complete.
Step 6: Second Iteration (i = 1)
- Check if City 1 has been visited:
visited[1] == true. - Since City 1 is already visited, skip DFS and move to the next iteration.
Step 7: Third Iteration (i = 2)
- Check if City 2 has been visited:
visited[2] == false. - Since City 2 has not been visited, initiate DFS starting from City 2.
- Increment the province count:
provinces = 2.
Step 8: DFS for City 2
- Mark City 2 as visited:
visited[2] = true→visited = [true, true, true]. - Check the connection between City 2 and other cities:
- City 2 to City 0:
isConnected[2][0] == 0, so skip. - City 2 to City 1:
isConnected[2][1] == 0, so skip. - City 2 to City 2: Already visited or same city, so skip.
- City 2 to City 0:
- DFS completes for City 2.
Step 9: Completion
- All cities have been visited. The total number of provinces is
2.
Final Output:
- The algorithm returns
2, indicating there are 2 distinct provinces in the given example.
Code
public class Solution {
// Function to traverse the connected cities using Depth First Search
private void dfs(int[][] isConnected, boolean[] visited, int i) {
for (int j = 0; j < isConnected.length; j++) {
if (isConnected[i][j] == 1 && !visited[j]) {
visited[j] = true;
dfs(isConnected, visited, j); // Recursive DFS call
}
}
}
public int findProvinces(int[][] isConnected) {
int provinces = 0; // Counter for number of provinces
int n = isConnected.length;
boolean[] visited = new boolean[n]; // Array to keep track of visited cities
for (int i = 0; i < n; i++) {
if (!visited[i]) {
dfs(isConnected, visited, i); // DFS call for every unvisited city
provinces++; // Increment province counter for every new DFS invocation
}
}
return provinces; // Return total provinces
}
// Main method for testing.
public static void main(String[] args) {
Solution solution = new Solution();
// Example 1:
int[][] example1 = { { 1, 1, 0 }, { 1, 1, 0 }, { 0, 0, 1 } };
System.out.println(solution.findProvinces(example1)); // Expected Output: 2
// Example 2:
int[][] example2 = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } };
System.out.println(solution.findProvinces(example2)); // Expected Output: 3
// Example 3:
int[][] example3 = {
{ 1, 0, 0, 1 },
{ 0, 1, 1, 0 },
{ 0, 1, 1, 0 },
{ 1, 0, 0, 1 },
};
System.out.println(solution.findProvinces(example3)); // Expected Output: 2
}
}
Time Complexity
-
Depth First Search (DFS): For a given node, the DFS will explore all of its neighbors. In the worst case, we may end up visiting all nodes in the graph starting from a single node. Hence, the DFS complexity is
, where (n) is the number of nodes. -
Overall Time Complexity: For each node, we might call DFS once (if that node is not visited before). Thus, the overall time complexity is
, with the DFS call being nested inside a loop that iterates over all nodes. In dense graphs where each node is connected to every other node, we will reach this upper bound.
Space Complexity
- Visited Array: This is an array of size (n) (the number of nodes), so its space requirement is
. - Recursive Call Stack: In the worst case, if all cities are connected in a linear manner (like a linked list), the maximum depth of recursive DFS calls will be (n). Hence, the call stack will take
space. - Overall Space Complexity: The dominant space-consuming factors are the
visitedarray and the recursive call stack. Hence, the space complexity is.
🤖 Don't fully get this? Learn it with Claude
Stuck on Number of Provinces? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.
Progressively stronger hints — you still solve it.
I'm working on the problem **Number of Provinces** (DSA). Give me a HINT LADDER: start with the tiniest nudge, then wait. Only reveal the next, stronger hint when I ask. Do NOT show the full solution unless I type 'show solution'. Keep me doing the thinking. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
See the technique, not just code.
Explain the optimal approach to **Number of Provinces** with a VISUAL walkthrough: trace it on a small concrete example using ASCII art / a step-by-step diagram, narrate what changes each step, then give time & space complexity with a one-line derivation. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Catch bugs, edge cases, sub-optimality.
I'll paste my solution to **Number of Provinces**. Review it for correctness, missed edge cases, and time/space complexity, then coach me toward the optimal — don't just rewrite it. Ask me to paste my code now. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Lock in recognition with look-alikes.
Give me 2 problems that use the SAME underlying pattern as **Number of Provinces**. For each, let me attempt first, then review my answer and name the trigger signal that reveals the pattern. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.