【题解】力扣765. 情侣牵手
765. 情侣牵手
题目来源
思路
如果一对情侣恰好坐在了一起,并且坐在了成组的座位上,其中一个下标一定是偶数,另一个一定是奇数,并且「偶数的值 + 1 = 奇数的值」。例如编号数对 [2, 3]
、[9, 8]
,这些数对的特点是除以 2(下取整)得到的数相等。
方法一、并查集
将N对情侣看做图中的\(N\)个节点;对于没对相邻的位置,如果第\(i\)对和第\(j\)对坐在了一起,则在 \(i\)号节点与 \(j\) 号节点之间连接一条边,代表需要交换这两对情侣的位置。
如果途中形成了一个大小为k的环:\(i\to j\to k\to ... \to l \to i\),则我们沿着环的方向,先交换\(i,j\)的位置,在交换\(j,k\)的位置,一次类推。在进行了\(k-1\)次交换后,这\(k\)对情侣就能够彼此牵手了。
故我们只需要利用并查集求出图中的每个连通分量;对于每个连通分量而言,其大小减 \(1\) 就是需要交换的次数。
我们最终需要的是N对情侣全部分开,所以 “交换之后的连通分量”就是N无疑,交换之前的连通分量就是我们\(uf.getCount()\), 假设为\(m\),即交换之前有m个连通分量。其中对于每个连通分量,假设各自的成员数(情侣对数)分别为 \(c_1, c_2, c_3......c_m\)。对于每一个连通分量,其最少交换次数都是\(n - 1\)次(都是循环交换),因此总最少交换次数就是$ (c_1 - 1) + (c_2 - 1) + (c_3 - 1) +......+ (c_m - 1)$。括号拆出来,再合并一下就变成了 \((c_1 + c_2 + c_3 +....+ c_m) - (1 + 1 + 1 +...... + 1)\),其中左边括号加起来就相当于是总情侣对数\(N\), 右边共有\(m\)个,如此最后最少交换次数就应该是 \(N - m\) 了。
代码
public class Solution {
public int minSwapsCouples(int[] row) {
int len = row.length;
int N = len / 2; // 所有情侣的数量
UnionFind unionFind = new UnionFind(N); // 初始化
for (int i = 0; i < row.length; i += 2) {
unionFind.union(row[i] / 2, row[i + 1] / 2);
}
return N - unionFind.getCount();
}
/**
* UnionFind并查集类
*/
public class UnionFind {
int[] parent;
int count; // 并查集里连通分量的个数(交换之前的连通分量)
int getCount() {
return count;
}
/*
按秩合并:
int[] rank;
初始化时:
在for循环里面:rank[i] = 1;
合并方法里:
int rootx = find(x);
int rooty = find(y);
if(rank[rootx] <= rank[rooty])
p[rootx] = rooty;
else
p[rooty] = rootx;
if(rank[rootx] == rank[rooty] && rootx != rooty)
rank[rooty]++; //如果深度相同且根节点不同,则新的根节点的深度+1
*/
// 初始化
UnionFind(int n) {
count = n;
parent = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}
// 查找
public int find(int x) {
while (x != parent[x]) {
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
/*
return p[x] == x ? p[x] : (p[x] = find(p[x]); // 路径压缩:p[x] = find(p[x]);
*/
}
// 合并
public void union(int x, int y) {
int rootx = find(x);
int rooty = find(y);
if (rootx == rooty) { // 是一对情侣
return;
}
parent[rootx] = rooty; // 合并
count--;
/*
p[find(x)] = find(y);
*/
}
}
}