位运算求数组中只出现一次的数

今天偶然看到的有趣的问题,有好几个系列,感受位运算的小魅力。
当然下面的题可以用map来简单算出,所以下面的题要求时间复杂度为O(n),空间复杂度为O(1)。

1. 简单版

一个数组中,只有一个数只出现一次,其他的数都是成双成对出现(2次,4次,6次…),求这出现一次的数。比如{1,2,3,1,2}求的是3。

熟悉异或运算的朋友,看到就会说so easy了。
就是利用异或的性质,两个相同的数异或结果为0,不同为1,同时异或与顺序无关,即满足交换率。
因此利用这个性质,可以对该数组所有的数进行异或操作,则成双成对的数异或后结果为0,得到的值就是只出现一次的值。
实现如下:

int getUnique(int a[])
{
    int ans = 0;
    for(int item: a)
        ans ^= item;
    return ans;
}

2. 普通版

一个数组中,只有一个数只出现一次,其他的数都是出现3次,求这出现一次的数。如{1, 2, 1, 1, 2, 3, 2} 求的就是3。

由于不再是成双成对,因此直接异或怕是解决不了了。不怕,还是可以用位运算来计算的,考虑的是二进制的表示,因为除了一个数,其他每个数都出现了3次,所以在这一位上对所有数求和后对3取余后,出现3次的数对3取余自然为0了,所以结果就是出现一次的数在这一位的值,所以只要累加就可以得到结果了。
说这么多废话,直接上个例子比较直观。

{1, 2, 1, 1, 2, 3, 2} 计算过程如下:
    0001
    0010
    0001
    0001
    0010
    0011
  + 0010
  ------
    0044
  % 3333
  ------
    0011  =》 3

看懂上面过程就很明确了,这里int为4个字节即32位。实现代码也很简单,如下:

int getUnique(int a[])
{
    for(int item: a){
        for(int i=0; i<32; i++){
            msum[i]+=(item>>i&1);   // 每个位的累加和
        }
    }
    int ans = 0;
    for(int i=0; i<32; i++){
        ans += (sum[i]<<i)%3;       // 注意每一位上的和要对3取余
    }
    return ans;
}

3. 升级版

一个数组中,只有两个数只出现一次,其他的数都是成双成对出现(2次,4次,6次…),求这出现一次的两个数。比如{1,4,3,1,5,5}求的就是4和3。

  • 似曾相熟,之前是只有一个数只出现一次,现在是两个数,同样的我们还是会将所有的数异或,显然最后得到的是这两个数的异或值。
    那么如何利用这个数的异或值来得到两个确切的值呢?
  • 如果能够把原数组分为两个子数组。在每个子数组中,包含一个只出现一次的数字,而其他数字都是成双成对的。如果能够这样拆分原数组,按照前面的办法就是分别求出这两个只出现一次的数字了。
  • 这样考虑,最后得到的两个不同的数的异或值,那这个异或值肯定不为0,因此可以以这个异或值最高位的1(计为第x位)分组,即将所有的数分成两个组,一组是x位是0的集合,另一组是x位是1的集合,这样这个数就分到了两组,成双成对的数也被分到了唯一的一组。
  • 因此现在就可以处理了,将这个异或值与其中一组异或,那么在这组出现一次的数就变成双成对,所以得到的值就是另一个数。同样对另一组异或得到另一个数。

废话还是有点多,还是用个例子介绍:

{1,4,3,1,5,5}
1^4^3^1^5^5 = 4^3 = 7 = 01112进制)
所以以第三位的值分成两组:
「第三位为0的分组」:{1,3,1}
「第三位为1的分组」:{4,5,5}
第一个数为: 7^1^3^1 = 4^3^1^3^1 = 4
第二个数为: 7^4^5^5 = 4^3^4^5^5 = 3

代码如下:

// 找出x第一个1出现的位置
int findFirstBit(int x)
{
    int indexBit = 0;
    while((x & 1 == 0) && (indexBit < 32)){
        x = x >> 1;
        indexBit++;
    }
    return indexBit;
}

void solve(int a[])
{
    int k = 0;      
    for(int item: a)
        k ^= item;  // 得到的是两个出现一次的值的异或值
    int indexBit = findFirstBit(k); // 第一个1出现的位
    int ans1 = k, ans2 = k;         // ans1和ans2分别对应两个只出现一次的数
    for(int item: a){
        if((item>>indexBit) & 1 == 0)
            ans1 ^= item;
        else
            ans2 ^= item;
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值