力扣765 情侣牵手 贪心+证明

765 情侣牵手

  • 链接:https://leetcode.cn/problems/couples-holding-hands/
  • 难度:困难
  • 标签:贪心、并查集

题目
n n n 对情侣坐在连续排列的 2 n 2n 2n 个座位上,想要牵到对方的手。
人和座位由一个整数数组 r o w row row 表示,其中 r o w [ i ] row[i] row[i] 是坐在第 i i i 个座位上的人的 ID。情侣们按顺序编号,第一对是 (0, 1),第二对是 (2, 3),以此类推,最后一对是 (2n-2, 2n-1)。
返回 最少交换座位的次数,以便每对情侣可以并肩坐在一起。 每次交换可选择任意两人,让他们站起来交换座位。

Solution:贪心

Explanation

要想让 n n n对情侣互相牵手,那么必须按照要求调换一部分人的次序,使所有的情侣可以互相牵手。要想让交换座位的次数最少,那么可以考虑一种做法:从前向后两两遍历数组,假设当前遍历到了 a a a b b b ,如果他们互为情侣,则跳过;如果不是情侣,则在后面的未遍历到的部分中找到 a a a 的伴侣,并将其与 b b b 进行交换。这种做法保证了“按需调换”,也就是只有当需要进行交换时才进行交换,此时的总的交换次数最少。但是贪心是一种比较想当然的做法,我们需要进行一些证明验证它的正确性。

证明

假设我们已经处理好了前 k - 1 对情侣,现在处理第 k 对位置上的人。有两种情况:

  1. 他们不是情侣:此时,我们在寻找第一个人的情侣,并且交换他和第二个人,这样第 k 对位置上就是情侣了,而且因为前 k - 1对情侣已经处理完毕了,所以第一个人的情侣必定是在后面,所以交换他们不会影响到已经处理好的情侣,因此只需要交换一次就可以使第 k 对位置上的人为情侣,这是最少的交换次数。
  2. 他们是情侣:那么不需要交换即可。
  3. 在处理完第 k 对位置后,继续处理第 k + 1 对位置,这个和处理第 k 对位置的方式完全一致,是一个规模更小的子问题。因此可以这样做。
小技巧

交换情侣时需要找到对应情侣的位置,才可以进行交换,此时有两个问题:

  1. 如何确定另一个情侣的编号?
    • 可以通过异或操作。例如(2,3),2 ^ 1 = 3, 3 ^ 1 = 2。那么第 i i i 位的另一个情侣的编号为 row[i] ^ 1
  2. 如何寻找另一个情侣的位置?
    • 朴素的做法是遍历,缺点是时间复杂度高,每次是 O ( n ) O(n) O(n)
    • 可以先使用哈希表对值与下标进行预处理,避免使用for循环以降低时间复杂度,每次时间复杂度降为 O ( 1 ) O(1) O(1)
    • 寻找到对应情侣后,注意也要交换他们在哈希表中的值。比如 h[4] = 4, h[8] = 9; 现在需交换他们,需要 h[4] = 9, f[8] = 4
Code
class Solution {
public:
    int minSwapsCouples(vector<int>& row) {
        int n = row.size();
        int ans = 0;
        unordered_map<int, int> hm;
        for (int i = 0; i < n; ++i)
            hm[row[i]] = i;
        for (int i = 0; i < n; i += 2) {
            if (row[i] != (row[i + 1] ^ 1)) {
                ans++;
                int t = hm[row[i] ^ 1];
                hm[row[i] ^ 1] = i + 1;
                hm[row[i + 1]] = t;
                swap(row[i + 1], row[t]);
            }
        }
        return ans;
    }
};

Time Complexity And Space Complexity

  • ⌛️时间复杂度: O ( n ) O(n) O(n)
  • 🌎空间复杂度: O ( n ) O(n) O(n)

Solution:并查集

Explanation

  • 需要知道一点:两对情侣坐错需要交换一次;三对情侣做错,需要交换两次;四队情侣坐错,需要交换三次;也就是说,n 对情侣坐错,需要交换 n - 1 次。
  • 假设有 2n 个人,那么就有 n 对情侣。我们根据情侣编号作为并查集的依据,比如某对情侣的各自编号为 8,9:那么此对情侣的编号就为 4:计算方法为 8 / 2 = 9 / 2 = 4. 也就是情侣编号为自己的值 / 2,这样每对情侣中的两个人计算得到的编号是一致的。
  • 我们就可以将每对位置上人按照他们对应的情侣编号合并起来,这样每个连通块有几个元素就代表有几对情侣互相坐错,最终答案就是每个连通块的元素个数减一之和。
Code
class Solution {
public:
    int minSwapsCouples(vector<int>& row) {
        int n = row.size(), m = n / 2;
        vector<int> fa(m);
        iota(fa.begin(), fa.end(), 0);
        vector<int> cnt(m, 1);
        for (int i = 0; i < n; i += 2) 
            merge(row[i] / 2, row[i + 1] / 2, fa, cnt);
        unordered_set<int> hs;
        int ans = 0;
        for (int i = 0; i < n; i += 2) {
            int fi = find(row[i] / 2, fa);
            if (hs.count(fi) == 0) {
                ans += cnt[fi] - 1;
                hs.insert(fi);
            }
        }
        return ans;
    }

    void merge(int x, int y, vector<int>& fa, vector<int>& cnt) {
        int fx = find(x, fa);
        int fy = find(y, fa);
        if (fx == fy)
            return;
        fa[fy] = fx;
        cnt[fx] += cnt[fy];
    }

    int find(int x, vector<int>& fa) {
        return x == fa[x] ? x : fa[x] = find(fa[x], fa);
    }
};

Time Complexity And Space Complexity

  • ⌛️时间复杂度: O ( N l o g N ) O(NlogN) 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)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值