Leetcode LCR 177. 撞色搭配

题目描述

整数数组 sockets 记录了一个袜子礼盒的颜色分布情况,其中 sockets[i] 表示该袜子的颜色编号。礼盒中除了一款撞色搭配的袜子,每种颜色的袜子均有两只。请设计一个程序,在时间复杂度 O(n),空间复杂度O(1) 内找到这双撞色搭配袜子的两个颜色编号。

示例 1:

输入:sockets = [4, 5, 2, 4, 6, 6]
输出:[2,5] 或 [5,2]

示例 2:

输入:sockets = [1, 2, 4, 1, 4, 3, 12, 3]
输出:[2,12] 或 [12,2]

提示:

  • 2 <= sockets.length <= 10000

 NOTE:题目要求时间复杂度 O(N) ,空间复杂度 O(1),因此首先排除 暴力法 和 哈希表统计法

 该题也就是说一个整型数组里除了两个数字之外,其他数字都出现了两次,请找出这两个只出现一次的数字

解题思路:利用异或运算^的特点;0与任何数异或等于该数字,任何数与自身异或等于0;且异或运算满足交换律;
解题步骤:

  1. 异或整个数组

    • 首先,通过对整个数组进行异或操作,你得到了两个只出现一次的数的异或结果。这个结果在二进制中的每个1都表示这两个数在该位上是不同的。
  2. 确定差异位

    • 接下来,并非与1进行异或,而是通过一个循环找出异或结果中最低位的1。这一步是通过与操作和位移实现的,具体操作是:
      • 初始化一个掩码变量m = 1(二进制表示为...0001)。
      • 通过while((z & m) == 0)循环左移这个掩码,直到z & m的结果不为0。这个结果不为0表示在这一位上,两个数是不同的。
  3. 分组

    • 使用找到的差异位(掩码m),将数组分成两组。这一步利用了与操作:
      • 如果数字与掩码m的与操作结果为0,表示这个数字在掩码m标记的位上为0。
      • 如果与操作结果不为0,表示这个数字在掩码m标记的位上为1。
  4. 组内异或操作

    • 对每组数字分别执行异或操作。由于除了一个唯一的数字外,每组的其他数字都出现两次,这意味着组内异或操作将取消所有成对的数字,只留下唯一的那个。
  5. 结果输出

    • 最后,每组的异或结果就是那两个只出现一次的不同的数字。

 假设我们有以下数组:

sockets = [4, 2, 4, 3, 2, 5]
在这个数组中,数字 42 各出现两次,而数字 35 各出现一次。我们的目标是找到这两个只出现一次的数字,即 35

步骤 1: 计算全部数字的异或 

 int n = 0;
for(int i : sockets) {
    n ^= i;
}

进行异或操作的结果(n)将是所有数字异或的结果。按照这个例子:

  • 开始:n = 0
  • n ^= 4 -> n = 4
  • n ^= 2 -> n = 6 (二进制 110)
  • n ^= 4 -> n = 2 (二进制 010)
  • n ^= 3 -> n = 1 (二进制 001)
  • n ^= 2 -> n = 3 (二进制 011)
  • n ^= 5 -> n = 6 (二进制 110)

 步骤 2: 找到第一个设置位(即差异位)

找最低位差异位即可;找差异位的目的就是将数组分为两个数组 因为通过异或只能取出一个不同的数,我们按照这两个数的差异位来分组,就一定可以将两个数分在两个不同的数组中,最后分别异或 就可以取出两个数

 int m = 1;
while((n & m) == 0) {
    m <<= 1;
}

找到二进制数 n = 6 (110) 的最低设置位:

  • n & 1110 & 001) = 0,所以 m 左移一位 -> m = 2 (010)
  • n & 2110 & 010) = 2,循环停止,找到最低设置位是第二位。

 步骤 3: 根据设置位分组并再次异或

int x = 0, y = 0;
for(int i : sockets) {
    if((i & m) == 0) {
        x ^= i;
    } else {
        y ^= i;
    }
}
 根据第二位是否设置,将数组分为两组并分别进行异或运算:

  • 4(二进制 100):第二位为0,所以 x ^= 4
  • 2(二进制 010):第二位为1,所以 y ^= 2
  • 4(二进制 100):第二位为0,所以 x ^= 4,现在 x = 0
  • 3(二进制 011):第二位为1,所以 y ^= 3
  • 2(二进制 010):第二位为1,所以 y ^= 2,现在 y = 1
  • 5(二进制 101):第二位为0,所以 x ^= 5

 最终结果:x = 5, y = 1

完整代码: 

#include <vector>
using namespace std;

class Solution {
public:
    // 函数sockCollocation接受一个整数数组sockets,并返回一个包含两个只出现一次的数字的向量
    vector<int> sockCollocation(vector<int>& sockets) {
        // 初始化变量n用来存储数组中所有元素的异或结果
        int n = 0;
        // 遍历数组,对所有元素进行异或操作
        // 相同的数字异或会取消彼此,留下的是两个只出现一次的数字的异或结果
        for(int i : sockets) {
            n ^= i;
        }

        // 初始化变量m作为掩码,用来找出n中第一个为1的位,即两个单独数字不同的那一位
        int m = 1;
        // 循环左移m直到找到n的一个为1的位
        // 这是因为n的该位为1表示两个只出现一次的数字在这一位上一个为0,一个为1
        while((n & m) == 0) {
            m <<= 1;
        }

        // 初始化x和y,用来最终存储两个只出现一次的数字
        int x = 0, y = 0;
        // 再次遍历数组,使用m将数组分为两组,并分别对这两组的数字进行异或操作
        for(int i : sockets) {
            // 如果数字i的m位为0,则归入一组
            if((i & m) == 0) {
                x ^= i;
            } else { // 否则归入另一组
                y ^= i;
            }
        }
        // 返回只出现一次的两个数字
        return vector<int> {x, y};
    } 
};

 

  • 13
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值