leetcode:547. 省份数量
题目
有 n
个城市,其中一些彼此相连,另一些没有相连。如果城市 a
与城市 b
直接相连,且城市 b
与城市 c
直接相连,那么城市 a
与城市 c
间接相连。
省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。
给你一个 n x n
的矩阵 isConnected
,其中 isConnected[i][j] = 1
表示第 i
个城市和第 j
个城市直接相连,而 isConnected[i][j] = 0
表示二者不直接相连。
返回矩阵中 省份 的数量。
示例 1:
输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
输出:2
示例 2:
输入:isConnected = [[1,0,0],[0,1,0],[0,0,1]]
输出:3
解答 & 代码
解法一:DFS
思路近似于[中等] 200. 岛屿数量,就是求连通分量的数量
- 设置一个
visited
数组,visited[i]
代表第i
个城市是否被访问过 遍历每个城市,如果该城市未被访问过,则从该城市开始深度优先搜索(DFS),将其所在连通分量的所有城市都标记为已访问:
通过邻接矩阵
isConnected
,访问所有与当前城市相邻的且未被访问过的城市,标记为已访问,再以这些城市为起点继续 DFS,直到该连通分量全部被标记已访问class Solution {
private:
// 从 idx 开始深度优先遍历,将其所在连通分量的虽有城市都标记为已访问
void dfs(vector<vector<int>>& isConnected, vector<bool>& visited, int idx)
{
int cityCnt = visited.size();
if(idx < 0 || idx >= cityCnt || visited[idx] == true)
return;
visited[idx] = true;
for(int next = 0; next < cityCnt; ++next)
{
if(isConnected[idx][next] == 1 && visited[next] == false)
dfs(isConnected, visited, next);
}
}
public:
int findCircleNum(vector<vector<int>>& isConnected) {
int cityCnt = isConnected.size(); // 城市数量(图的节点数量)
int provinceCnt = 0; // 省份数量(连通分量数量)
vector<bool> visited(cityCnt, false); // visited[i] 代表第 i 个城市是否已被访问过
// 遍历每个城市
for(int i = 0; i < cityCnt; ++i)
{
// 若当前城市未被访问过,则从当前城市开始 DFS,将其所在连通分量的所有城市都标记为已访问
if(visited[i] == false)
{
++provinceCnt; // 省份数量(连通分量数量)+1
dfs(isConnected, visited, i);
}
}
return provinceCnt;
}
};
复杂度分析:设有 n 个城市
时间复杂度
:遍历邻接矩阵
isConnected
的每个元素- 空间复杂度 O(n):
visited
数组长为 n;递归栈深度不超过 n
执行结果:
执行结果:通过
执行用时:20 ms, 在所有 C++ 提交中击败了 71.06% 的用户
内存消耗:13.4 MB, 在所有 C++ 提交中击败了 57.60% 的用户
解法二:并查集
设城市数量(节点数量)为 n
- 建立包含
n
个节点的并查集 - 遍历邻接矩阵
isConnected
的上三角(不含对角线),如果isConnected[i][j] == 1
,说明节点i
和节点j
相连,则合并两个节点所在的连通分支
省份数量(连通分支数量) = 城市数量(节点数量) - 合并次数
或者统计并查集中根节点数量,即 parents[i] == i 的数量,就是连通分支数量
/* 并查集类 */
class UnionFind {
private:
vector<int> parents; // 父节点数组,parent[i] 代表节点 i 的父节点
public:
/* 构造函数 */
UnionFind(int n)
{
parents.resize(n);
for(int i = 0; i < n; ++i) // 将每个节点的父节点都初始化为自己本身
parents[i] = i;
}
/* 递归查询节点 x 的根节点并返回,寻找根节点的过程进行路径压缩 */
int find(int x)
{
// 递归结束条件:如果节点 x 的父节点是 x 本身,说明 x 已经是根节点,直接返回
if(x == parents[x])
return x;
parents[x] = find(parents[x]); // 递归寻找 x 的根节点
return parents[x];
}
/* 合并操作:x 在一个连通图中,y 在一个连通图中,x 和 y 相连,合并两个连通图 */
void _union(int x, int y, int& mergeCnt)
{
int rootX = find(x); // x 的根节点
int rootY = find(y); // y 的根节点
if(rootX == rootY) // 如果节点 x、y 的根节点相同,则无需合并
return;
// 合并两个连通图
++mergeCnt; // 合并次数 + 1
parents[rootX] = rootY; // 将 x 的根节点指向 y 的根节点
}
};
class Solution {
public:
int findCircleNum(vector<vector<int>>& isConnected) {
int cityCnt = isConnected.size(); // 城市数量(图中节点数量)
UnionFind uf(cityCnt); // 并查集
int mergeCnt = 0; // 合并次数
// 遍历邻接矩阵 isConnected 的上三角(不含对角线)
for(int i = 0; i < cityCnt; ++i)
{
for(int j = i + 1; j < cityCnt; ++j)
{
if(isConnected[i][j] == 1) // 若点 i 和 j 相连,则合并 i 和 j 的连通图
uf._union(i, j, mergeCnt);
}
}
// 省份数量(连通分支数量) = 城市数量(节点数量) - 合并次数
int provinceCnt = cityCnt - mergeCnt;
return provinceCnt;
}
};
复杂度分析:设有 n 个城市
- 时间复杂度
:遍历邻接矩阵
isConnected
上三角的每个元素,时间复杂度;如果该元素为 1,则需要调用
_union
进行合并,_union
中又要调用两次find
递归查找根节点,时间复杂度为 O(log n) - 空间复杂度 O(n):并查集中
parents
数组长为 n
执行结果:
执行结果:通过
执行用时:20 ms, 在所有 C++ 提交中击败了 71.06% 的用户
内存消耗:13.4 MB, 在所有 C++ 提交中击败了 48.65% 的用户