1319.连通网络的操作次数
思路一:并查集
好吧,我不说了,这个月看到题目第一个思路就是并查集。
主要分两类情况讨论:
- 鉴于连接n个电脑至少要n-1条线,所以如果线的数量 connections.length < 电脑数量 n-1 ,则我们输出(-1)表示一定不可能。
- 否则,我们直到现在的线的条数一定大于等于n-1条。
我们注意到,每个含有 n i n_i ni台电脑的Cluster(即相连通的电脑群)只需要 n i − 1 n_i-1 ni−1条线,记 C C C为所有Cluster的集合,则包含 ∣ C ∣ |C| ∣C∣个Cluster至少需要
∑ i ∈ ∣ C ∣ ( n i − 1 ) = ∑ i ∈ ∣ C ∣ n i − ∣ C ∣ = n − ∣ C ∣ \sum_{i \in |C|} (n_i -1) = \sum_{i\in|C|} n_i -|C| = n-|C| i∈∣C∣∑(ni−1)=i∈∣C∣∑ni−∣C∣=n−∣C∣
条线。
又因为,我们这种情况下一定能连接,所以至少有 n − 1 n-1 n−1条线,所以,一定有 ( n − 1 ) − ( n − ∣ C ∣ ) = ∣ C ∣ − 1 (n-1)-(n-|C|)=|C|-1 (n−1)−(n−∣C∣)=∣C∣−1条线多出来,我们用每一条线就可以减少一个Cluster,因此,只要用 ∣ C ∣ − 1 |C|-1 ∣C∣−1条线就可以将原先 ∣ C ∣ |C| ∣C∣个类别变成1个类别。
因此,我们这种情况下返回的是 ∣ C ∣ − 1 |C|-1 ∣C∣−1。
class Solution {
public int makeConnected(int n, int[][] connections) {
// 如果线的数量 connections.length < 电脑数量 n-1
if(connections.length < n-1)
return -1;
// 我感觉需要转接的线就等于 总类别(联通算一类)数量-1。
UnionFind uf = new UnionFind(n);
for(int[] connection : connections)
uf.union(connection[0],connection[1]);
return uf.countClasses()-1;
}
}
class UnionFind{
int[] elements;
int[] rank;
public UnionFind(int N){
this.elements = new int[N];
this.rank = new int[N];
for(int i=0;i<N;i++)
this.elements[i]=i;
}
public int find(int i){
if(elements[i]==i)
return i;
else{
elements[i]=find(elements[i]);
return elements[i];
}
}
public void union(int i, int j){
int parenti = find(i);
int parentj = find(j);
if(rank[parenti]<rank[parentj])
elements[parenti]=parentj;
else if(rank[parenti]>rank[parentj])
elements[parentj]=parenti;
else{
elements[parentj]=parenti;
rank[parenti]++;
}
}
public int countClasses(){
int res=0;
for(int i=0;i<elements.length;i++)
if(elements[i]==i)
res++;
return res;
}
}
思路二:DFS
- 第一步:构建邻接表
- 第二步:用DFS找连通分量
class Solution {
List<Integer>[] edges; // 用来记录和当前节点相邻的节点
boolean[] used; //用来记录每个顶点是否被经过了
public int makeConnected(int n, int[][] connections) {
// 如果线的数量 connections.length < 电脑数量 n-1
if(connections.length < n-1)
return -1;
used = new boolean[n];
edges = new List[n];
for(int i=0;i<n;i++)
edges[i] = new ArrayList<Integer>();
for(int[] connection : connections){
edges[connection[0]].add(connection[1]);
edges[connection[1]].add(connection[0]);
}
int classes = 0; //用来记录类别数量
for(int i=0;i<n;i++){
// 用DFS将没有被经过的节点的邻居全遍历一遍
if(!used[i]){
dfs(i);
classes++;
}
}
return classes-1;
}
public void dfs(int i){
used[i] = true;
for(Integer neighbor : edges[i]){
if(!used[neighbor])
dfs(neighbor);
}
}
}
收获:DFS的模版
List<Integer>[] edges; //edges[i]存储和顶点i相连的顶点的编号
boolean[] visited; // 记录点i是否遍历过
// 初始化edges
edges = new List[n]; // n为所有顶点数量
for(int i=0;i<n;i++)
edges = new ArrayList<Integer>();
// 初始化权重
for(...)
edges[i].add(j);
edges[j].add(i);
public void dfs(int i){
visited[i] = true;
for(Integer neighbor: edges[i]){
if(!visited[neighbor]){
dfs(neighbor);
}
}
}