
Problem Statement
In this problem, we are working with a representation of cities and their connections. We represent n
cities using an n x n
matrix named isConnected
. In this matrix, if isConnected[i][j] = 1
, it means there is a direct connection between city i
and city j
. Conversely, isConnected[i][j] = 0
indicates no direct connection. This setup allows any city to be connected either directly or indirectly to other cities. A province is defined as a group of cities that are all directly or indirectly connected to one another, without links to any cities outside this group.
Your task is to determine the total number of such provinces. For every set of interconnected cities that form a self-contained network, it counts as one province.
Examples
Example 1
Input:
isConnected = [[1,1,0],[1,1,0],[0,0,1]]
Output:
2
Explanation:
Cities 0 and 1 are connected to each other. City 2 is isolated. So there are 2 provinces: [0,1] and [2].
Example 2
Input:
isConnected = [[1,0,0],[0,1,0],[0,0,1]]
Output:
3
Explanation:
Each city is isolated and forms its own province. So there are 3 provinces: [0], [1], and [2].
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]
Approach and Intuition
Understanding how cities form provinces can be approached by visualizing this as a graph problem, where each city represents a node and each direct connection represents an edge.
To solve the problem, we can employ Depth-First Search (DFS), Breadth-First Search (BFS), or Union-Find. These techniques efficiently identify connected components (provinces) in a graph.
Steps to Approach:
Initialize a counter for provinces.
Use an auxiliary structure (like a boolean array) to track visited cities.
Iterate through each city:
- If the city has not been visited, it starts a new province.
- Increment the province counter.
- Launch a DFS or BFS from this city to visit all directly or indirectly connected cities, marking them as visited.
At the end of the traversal, the counter reflects the total number of provinces.
DFS Approach
DFS works by exploring as far as possible along each branch before backtracking.
Union-Find Approach
Union-Find (Disjoint Set Union) groups cities into sets. Initially, each city is its own group. For every connection, cities are merged into the same group. The number of unique groups at the end equals the number of provinces.
Either approach works efficiently within the problem's constraints.
Solutions
- C++
- Java
- Python
class DisjointSet {
private:
vector<int> lead, height;
int componentCount;
public:
DisjointSet(int elements) {
lead.resize(elements);
height.resize(elements, 0);
componentCount = elements;
for (int i = 0; i < elements; i++) {
lead[i] = i;
}
}
int findLeader(int x) {
if (lead[x] != x) lead[x] = findLeader(lead[x]);
return lead[x];
}
void connect(int x, int y) {
int rootX = findLeader(x), rootY = findLeader(y);
if (height[rootX] < height[rootY]) {
lead[rootX] = rootY;
} else if (height[rootX] > height[rootY]) {
lead[rootY] = rootX;
} else {
lead[rootY] = rootX;
height[rootX]++;
}
}
};
class Solution {
public:
int findCircleNum(vector<vector<int>>& isConnected) {
int cities = isConnected.size();
DisjointSet ds(cities);
int clusters = cities;
for (int i = 0; i < cities; i++) {
for (int j = i + 1; j < cities; j++) {
if (isConnected[i][j] && ds.findLeader(i) != ds.findLeader(j)) {
clusters--;
ds.connect(i, j);
}
}
}
return clusters;
}
};
In the provided C++ code, the problem of finding the number of provinces among cities connected by roads is addressed using a Disjoint Set (Union-Find) data structure. The main objectives are to determine if two cities are directly or indirectly connected, and to compute groups of connected cities, which are termed as provinces.
The code includes two main components:
DisjointSet Class: This class is specially designed to keep track of the elements that are leaders of their set and to perform efficient unions of sets.
Key Methods in DisjointSet:
DisjointSet(int elements)
: Initializes a set for each element where each element is its own leader and the height (for union by rank) is set to zero.findLeader(int x)
: Retrieves the root leader of the set to which elementx
belongs, using path compression for optimization.connect(int x, int y)
: Connects the sets ofx
andy
. Ifx
andy
have different root leaders, the trees are unioned by height to maintain balanced tree structures.
Solution Class: This class uses an instance of
DisjointSet
to solve the problem by determining the number of connected components (provinces).Method in Solution:
findCircleNum(vector<vector<int>>& isConnected)
: Examines a matrix whereisConnected[i][j]
indicates if cityi
is directly connected to cityj
. The method checks all pairs of cities, and if they are connected but belong to different sets, a union operation is performed. After processing all city connections, the number of disjoint sets (provinces) is calculated by counting how many unique leaders exist.
The algorithm initiates with treating each city as its own province. As connections are discovered (direct or indirect), the number of provinces decreases through the union operations, counting the remaining distinct sets using the clusters
variable.
This setup efficiently determines groups of interconnected cities, representing how many separate connected groupings (provinces) are present, ensuring optimal complexity and performance using the Disjoint Set structure.
class DisjointSet {
int[] leader;
int[] size;
public DisjointSet(int length) {
leader = new int[length];
for (int i = 0; i < length; i++) leader[i] = i;
size = new int[length];
}
public int find(int p) {
if (leader[p] != p) leader[p] = find(leader[p]);
return leader[p];
}
public void link(int p, int q) {
int rootP = find(p), rootQ = find(q);
if (rootP == rootQ) {
return;
} else if (size[rootP] < size[rootQ]) {
leader[rootP] = rootQ;
} else if (size[rootP] > size[rootQ]) {
leader[rootQ] = rootP;
} else {
leader[rootQ] = rootP;
size[rootP]++;
}
}
}
class Solution {
public int findCircleNum(int[][] isConnected) {
int numProvinces = isConnected.length;
DisjointSet ds = new DisjointSet(numProvinces);
int componentsCount = numProvinces;
for (int i = 0; i < numProvinces; i++) {
for (int j = i + 1; j < numProvinces; j++) {
if (isConnected[i][j] == 1 && ds.find(i) != ds.find(j)) {
componentsCount--;
ds.link(i, j);
}
}
}
return componentsCount;
}
}
This summary explains the Java solution to the problem of finding the number of provinces represented as a graph of connected cities. The code uses the Disjoint Set (Union-Find) data structure to dynamically manage the grouping of individual cities into provinces based on a connectivity matrix.
The
DisjointSet
class initializes with each city as its own leader, indicating it forms a separate province initially. Methods includefind
, which returns the representative leader of a city using path compression to flatten the structure, andlink
that unites two sets based on their size to keep the data structure optimally flat.The
Solution
class methodfindCircleNum
calculates the number of provinces by iterating through the upper half of the symmetric connectivity matrix. Connected cities are unioned in the disjoint set, reducing the component count each time cities from different sets are connected.Each time two cities are found to be connected (
isConnected[i][j] == 1
), check if they belong to the same province already usingfind
. If they don't, link them usinglink
, effectively merging the components they belong to.
This implementation effectively counts the distinct provinces by managing union operations efficiently through the union-by-size and path compression strategies of the disjoint set.
class DisjointSet:
def __init__(self, size):
self.root = list(range(size))
self.tree_height = [0] * size
def root_find(self, x):
if self.root[x] != x:
self.root[x] = self.root_find(self.root[x])
return self.root[x]
def merge(self, x, y):
root_x = self.root_find(x)
root_y = self.root_find(y)
if self.tree_height[root_x] < self.tree_height[root_y]:
self.root[root_x] = root_y
elif self.tree_height[root_x] > self.tree_height[root_y]:
self.root[root_y] = root_x
else:
self.root[root_y] = root_x
self.tree_height[root_x] += 1
class ProvinceCounter:
def countProvinces(self, isConnected):
count_nodes = len(isConnected)
ds = DisjointSet(count_nodes)
province_count = count_nodes
for i in range(count_nodes):
for j in range(i + 1, count_nodes):
if isConnected[i][j] == 1 and ds.root_find(i) != ds.root_find(j):
province_count -= 1
ds.merge(i, j)
return province_count
The provided Python code implements a solution to the problem of determining the number of provinces, where each province is represented as a connected component in an undirected graph. The solution uses a disjoint set (or union-find) data structure.
- Python classes
DisjointSet
andProvinceCounter
are defined. - The
DisjointSet
class manages the union-find operations with methods to find the root of an element (root_find
) and to union two subsets (merge
). - The
ProvinceCounter
class includes a methodcountProvinces
that calculates the number of provinces based on theisConnected
matrix:- Each element
i, j
in the matrix indicates whether nodei
is directly connected to nodej
. - An instance of
DisjointSet
is initialized with the number of nodes. - The
countProvinces
method iterates over each element in the matrix, performing a merge operation for directly connected nodes and decrementing the province count for each successful union. - The method finally returns the total number of distinct provinces, which correspond to the number of connected components in the graph.
- Each element
This solution efficiently groups and counts connected components in the graph using union-find, which is optimal for such problems that require determining connectedness and component counts in a graph-based structure.
No comments yet.