以下为力扣的官方题解
N N N 对情侣坐在连续排列的 2 N 2N 2N 个座位上,想要牵到对方的手。 计算最少交换座位的次数,以便每对情侣可以并肩坐在一起。 一次交换可选择任意两人,让他们站起来交换座位。
人和座位用 0 0 0 到 2 N − 1 2N-1 2N−1 的整数表示,情侣们按顺序编号,第一对是 ( 0 , 1 ) (0, 1) (0,1),第二对是 ( 2 , 3 ) (2, 3) (2,3),以此类推,最后一对是 ( 2 N − 2 , 2 N − 1 ) (2N-2, 2N-1) (2N−2,2N−1)。
这些情侣的初始座位 r o w [ i ] row[i] row[i] 是由最初始坐在第 i i i 个座位上的人决定的。
输入: r o w = [ 0 , 2 , 1 , 3 ] row = [0, 2, 1, 3] row=[0,2,1,3]
输出: 1 1 1
解释: 我们只需要交换 r o w [ 1 ] row[1] row[1] 和 r o w [ 2 ] row[2] row[2] 的位置即可。
输入: r o w = [ 3 , 2 , 0 , 1 ] row = [3, 2, 0, 1] row=[3,2,0,1]
输出: 0 0 0
解释: 无需交换座位,所有的情侣都已经可以手牵手了。
- l e n ( r o w ) len(row) len(row) 是偶数且数值在 [ 4 , 60 ] [4, 60] [4,60] 范围内。
- 可以保证 r o w row row 是序列 0... l e n ( r o w ) − 1 0...len(row)-1 0...len(row)−1 的一个全排列。
假定第一对情侣的男生与第二对情侣的女生坐在了一起,而第二对情侣的男生与第三对情侣的女生坐在了一起。根据题意,要想让第二对情侣之间能够成功牵手,要么交换第一对情侣的男生与第二对情侣的男生,要么交换第二对情侣的女生与第三对情侣的女生。
既然存在这两种交换方式,那么有必要两种方式都考虑吗?答案是无需都考虑。不难注意到,无论采用了两种交换方式中的哪一种,最后的结局都是「第二对情侣坐在了一起,且第一对情侣的男生与第三对情侣的女生坐在了一起」,因此两种交换方式是等价的。
因此,我们将 N N N 对情侣看做图中的 N N N 个节点;对于每对相邻的位置,如果是第 i i i 对与第 j j j 对坐在了一起,则在 i i i 号节点与 j j j 号节点之间连接一条边,代表需要交换这两对情侣的位置。
如果图中形成了一个大小为 k k k 的环: i → j → k → … → l → i i \rightarrow j \rightarrow k \rightarrow \ldots \rightarrow l \rightarrow i i→j→k→…→l→i,则我们沿着环的方向,先交换 i , j i,j i,j 的位置,再交换 j , k j,k j,k 的位置,以此类推。在进行了 k − 1 k-1 k−1 次交换后,这 k k k 对情侣就都能够彼此牵手了。
故我们只需要利用并查集求出图中的每个连通分量;对于每个连通分量而言,其大小减 1 1 1 就是需要交换的次数。
class Solution {
public int minSwapsCouples(int[] row) {
int n = row.length;
int tot = n/2;
int[] f = new int[tot];
for (int i=0; i<tot; i++)
{
f[i] = i;
}
for (int i=0; i<n; i+=2)
{
int l = row[i]/2;
int r = row[i+1]/2;
add(f, l, r);
}
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (int i=0; i<tot; i++)
{
int fx = getf(f, i);
map.put(fx, map.getOrDefault(fx, 0)+1);
}
int ret = 0;
for (Map.Entry<Integer, Integer> entry : map.entrySet())
{
ret += entry.getValue()-1;
}
return ret;
}
public int getf(int[] f, int x) {
if (f[x] == x)
{
return x;
}
int newf = getf(f, f[x]);
f[x] = newf;
return newf;
}
public void add(int[] f, int x, int y) {
int fx = getf(f, x);
int fy = getf(f, y);
f[f[x]] = fy;
}
}
我们也可以通过广度优先搜索的方式,求解图中的连通分量。
起初,我们将每个节点都标记为「未访问」,并遍历图中的每个节点。如果发现一个「未访问」的节点,就从该节点出发,沿着图中的边,将其余的「未访问」的节点都标记为「已访问」,并同时统计标记的次数。当遍历过程终止时,标记的数量次数即为连通分量的大小。
class Solution {
public int minSwapsCouples(int[] row) {
int n = row.length;
int tot = n/2;
List<Integer>[] graph = new List[tot];
for (int i=0; i<tot; i++)
{
graph[i] = new ArrayList<Integer>();
}
for (int i=0; i<n; i+=2)
{
int l = row[i]/2;
int r = row[i+1]/2;
if (l != r)
{
graph[l].add(r);
graph[r].add(l);
}
}
boolean[] visited = new boolean[tot];
int ret = 0;
for (int i=0; i<tot; i++)
{
if (!visited[i])
{
Queue<Integer> queue = new LinkedList<Integer>();
visited[i] = true;
queue.offer(i);
int cnt = 0;
while (!queue.isEmpty())
{
int x = queue.poll();
cnt ++;
for (int y : graph[x])
{
if (!visited[y])
{
visited[y] = true;
queue.offer(y);
}
}
}
ret += cnt-1;
}
}
return ret;
}
}