leetcode:547. 省份数量
题目
有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。
省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。
给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。
返回矩阵中 省份 的数量。
示例 1:![[中等] 547. 省份数量 - 图1](/uploads/projects/liangduo-rjrcs@ggu4wq/9225c7f508765cb2002f9da93e93fa04.jpeg)
输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]输出:2
示例 2:![[中等] 547. 省份数量 - 图2](/uploads/projects/liangduo-rjrcs@ggu4wq/21a540e1232b494bde280a0ca1b7f100.jpeg)
输入: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; // 省份数量(连通分量数量)+1dfs(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; // 合并次数 + 1parents[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% 的用户
