【力扣】765. 情侣牵手

以下为力扣的官方题解

765. 情侣牵手

  • 题目
  • 示例1
  • 示例2
  • 说明
  • 官方题解
    • 思路一 并查集
      • 代码
      • 复杂度分析
    • 思路二 广度优先搜索
      • 代码
      • 复杂度分析

题目

N N N 对情侣坐在连续排列的 2 N 2N 2N 个座位上,想要牵到对方的手。 计算最少交换座位的次数,以便每对情侣可以并肩坐在一起。 一次交换可选择任意两人,让他们站起来交换座位。
人和座位用 0 0 0 2 N − 1 2N-1 2N1 的整数表示,情侣们按顺序编号,第一对是 ( 0 , 1 ) (0, 1) (0,1),第二对是 ( 2 , 3 ) (2, 3) (2,3),以此类推,最后一对是 ( 2 N − 2 , 2 N − 1 ) (2N-2, 2N-1) (2N2,2N1)
这些情侣的初始座位 r o w [ i ] row[i] row[i] 是由最初始坐在第 i i i 个座位上的人决定的。

示例1

输入: 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] 的位置即可。

示例2

输入: r o w = [ 3 , 2 , 0 , 1 ] row = [3, 2, 0, 1] row=[3,2,0,1]
输出: 0 0 0
解释: 无需交换座位,所有的情侣都已经可以手牵手了。

说明

  1. l e n ( r o w ) len(row) len(row) 是偶数且数值在 [ 4 , 60 ] [4, 60] [4,60] 范围内。
  2. 可以保证 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 ijkli,则我们沿着环的方向,先交换 i , j i,j i,j 的位置,再交换 j , k j,k j,k 的位置,以此类推。在进行了 k − 1 k-1 k1 次交换后,这 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;
    }
}

复杂度分析

  • 时间复杂度: O ( N log ⁡ N ) O(N \log N) O(NlogN),其中 N N N 为情侣的总数。这里的并查集使用了路径压缩,但是没有使用按秩合并,最坏情况下的时间复杂度是 O ( N log ⁡ N ) O(N \log N) O(NlogN),平均情况下的时间复杂度依然是 O ( N α ( N ) ) O(N \alpha (N)) O(Nα(N)),其中 α \alpha α 为阿克曼函数的反函数, α ( N ) \alpha (N) α(N) 可以认为是一个很小的常数。
  • 空间复杂度: O ( N ) O(N) O(N)

思路二 广度优先搜索

我们也可以通过广度优先搜索的方式,求解图中的连通分量。

起初,我们将每个节点都标记为「未访问」,并遍历图中的每个节点。如果发现一个「未访问」的节点,就从该节点出发,沿着图中的边,将其余的「未访问」的节点都标记为「已访问」,并同时统计标记的次数。当遍历过程终止时,标记的数量次数即为连通分量的大小。

代码

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;
    }
}

复杂度分析

  • 时间复杂度: O ( N ) O(N) O(N),其中 N N N 为情侣的总数。每个节点最多只被标记 1 1 1 次。
  • 空间复杂度: O ( N ) O(N) O(N),其中 N N N 为情侣的总数。为队列的开销。

你可能感兴趣的:(力扣,java,leetcode)