0% completed
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 i<sup>th</sup> city and the j<sup>th</sup> 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
1
and2
form a single provenance, and city3
is 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 <= 200
n == isConnected.length
n == isConnected[i].length
isConnected[i][j]
is1
or0
.isConnected[i][i] == 1
isConnected[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
parent
andrank
arrays. - Initialize the
parent
array such that each node is its own parent. - Initialize the
rank
array 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
find
operation to check the roots of nodesi
andj
. - If the roots are different, decrement the
numberOfProvinces
and perform theunion
operation to merge the sets containing nodesi
andj
.
- Use the
- If
- For each pair of nodes (i, j) in the adjacency matrix
-
Return the Result:
- The final value of
numberOfProvinces
represents 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
numberOfProvinces
to 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
Complexity Analysis
Time Complexity
-
Initialization:
- Initializing the
parent
andrank
arrays takes O(N) time, where (N) is the number of nodes (or cities).
- Initializing the
-
Find Operation:
- The
find
operation with path compression has an amortized time complexity of O(\alpha(N)), where \alpha(N) 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_set
operation involves twofind
operations and one assignment, leading to a time complexity of O(\alpha(N)) per union.
- The
-
Processing Connections:
- The nested loops iterate through each pair of nodes to check for connections. This involves O(N^2) iterations, where each iteration involves at most two
find
operations and oneunion_set
operation. Thus, the total time complexity for processing all connections is O(N^2 \alpha(N)).
- The nested loops iterate through each pair of nodes to check for connections. This involves O(N^2) iterations, where each iteration involves at most two
Given that \alpha(N) is nearly constant, the overall time complexity can be simplified to O(N^2).
Space Complexity
- Parent and Rank Arrays:
- The
parent
andrank
arrays require O(N) space 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
provinces
to0
. This will count the number of distinct provinces. - Create a
visited
array 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
i
from0
ton-1
(wheren
is 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
provinces
counter, as this city marks the start of a new province. - Call the
dfs
method to explore all cities connected to cityi
.
- Loop through each city
-
Depth-First Search (DFS):
- In the
dfs
method, start with cityi
. - Loop through each city
j
from0
ton-1
. - If city
i
is connected to cityj
(isConnected[i][j] == 1
) and cityj
has not been visited (!visited[j]
), mark cityj
as visited (visited[j] = true
). - Recursively call the
dfs
method with cityj
as 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
findCircleNum
method 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 = 0
visited = [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] == 1
andvisited[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
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 O(n), 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 O(n^2), 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 O(n).
- 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 O(n) space.
- Overall Space Complexity: The dominant space-consuming factors are the
visited
array and the recursive call stack. Hence, the space complexity is O(n).
.....
.....
.....
Table of Contents
Contents are not accessible
Contents are not accessible
Contents are not accessible
Contents are not accessible
Contents are not accessible