题目来源于leetcode,解法和思路仅代表个人观点。传送门。
难度:中等
时间:10min
题目
用以太网线缆将 n 台计算机连接成一个网络,计算机的编号从 0 到 n-1。线缆用 connections 表示,其中 connections[i] = [a, b] 连接了计算机 a 和 b。
网络中的任何一台计算机都可以通过网络直接或者间接访问同一个网络中其他任意一台计算机。
给你这个计算机网络的初始布线 connections,你可以拔开任意两台直连计算机之间的线缆,并用它连接一对未直连的计算机。请你计算并返回使所有计算机都连通所需的最少操作次数。如果不可能,则返回 -1 。
示例 1:
输入:n = 4, connections = [[0,1],[0,2],[1,2]]
输出:1
解释:拔下计算机 1 和 2 之间的线缆,并将它插到计算机 1 和 3 上。
示例 2:
输入:n = 6, connections = [[0,1],[0,2],[0,3],[1,2],[1,3]]
输出:2
提示:
1 <= n <= 105
1 <= connections.length <= min(n*(n-1)/2, 105)
connections[i].length == 2
0 <= connections[i][0], connections[i][1] <n
connections[i][0] != connections[i][1]
没有重复的连接。
两台计算机不会通过多条线缆连接。
思路
无向图连通性问题,首先考虑并查集
并查集
- 遍历边的集合connections,依次合并结点
2. 计算所有冗余边的条数
冗余边 是 加上后,图的连通性没有发生变化
- 遍历所有结点n
4. 消耗一条冗余边【合并】两个不在同一个集合的结点
5. 如果冗余边不足,返回-1
- 否则,返回 【合并】次数
代码
class Solution {
public:
//并查集
class UnionFind{
public:
vector<int> parent;
UnionFind(int N){
parent.resize(N);
for(int i=0;i<N;i++){
parent[i] = i;
}
}
void join(int x,int y){
int rootX = find(x);
int rootY = find(y);
parent[rootX] = rootY;
}
int find(int x){
while(parent[x] != x){
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
bool isConnected(int x,int y){
return find(x) == find(y);
}
};
int makeConnected(int n, vector<vector<int>>& connections) {
//并查集
UnionFind unionFind(n);
//冗余的边
int redundancy = 0;
//答案
int ans = 0;
//遍历每一条边
for(int i=0;i<connections.size();i++){
int x = connections[i][0];
int y = connections[i][1];
if(!unionFind.isConnected(x,y)){
//如果不在一个集合就合并
unionFind.join(x,y);
}else{
//如果在一个集合,这条边冗余
redundancy++;
}
}
//遍历每个结点
for(int i=1;i<n;i++){
if(!unionFind.isConnected(0,i)){
//如果不在一个集合就消耗一条冗余边合并
if(--redundancy<0){
//冗余边不足
return -1;
}
unionFind.join(0,i);
ans++;
}
}
return ans;
}
};
算法复杂度
时间复杂度: O(
m
⋅
α
(
n
)
m \cdot \alpha(n)
m⋅α(n)),m为边的条数,
α
\alpha
α是反阿克曼函数
空间复杂度: O(n),n为结点数
在并查集中,查找和合并的耗时是反阿克曼函数,近似于O(1)