学习笔记——利用位运算求数组中只出现一次或奇数次的数字

2013.10.24 早上

问题:利用位运算求数组中只出现一次或奇数次的数字

问题描述:

该问题是我在网易博主何海涛的博文上转载而来,分析部分源自作者的博文用于学习,代码部分是我本人参照是实现的,谢谢指正,其版权归何海涛所有

1.  数组中只出现一次的两个数字

分析:

题目:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

分析:这是一道很新颖的关于位运算的面试题。

首先我们考虑这个问题的一个简单版本:一个数组里除了一个数字之外,其他的数字都出现了两次。请写程序找出这个只出现一次的数字。

这个题目的突破口在哪里?题目为什么要强调有一个数字出现一次,其他的出现两次?我们想到了异或运算的性质:任何一个数字异或它自己都等于0。也就是说,如果我们从头到尾依次异或数组中的每一个数字,那么最终的结果刚好是那个只出现依次的数字,因为那些出现两次的数字全部在异或中抵消掉了。

有了上面简单问题的解决方案之后,我们回到原始的问题。如果能够把原数组分为两个子数组。在每个子数组中,包含一个只出现一次的数字,而其他数字都出现两次。如果能够这样拆分原数组,按照前面的办法就是分别求出这两个只出现一次的数字了。

我们还是从头到尾依次异或数组中的每一个数字,那么最终得到的结果就是两个只出现一次的数字的异或结果。因为其他数字都出现了两次,在异或中全部抵消掉了。由于这两个数字肯定不一样,那么这个异或结果肯定不为0,也就是说在这个结果数字的二进制表示中至少就有一位为1。我们在结果数字中找到第一个为1的位的位置,记为第N位。现在我们以第N位是不是1为标准把原数组中的数字分成两个子数组,第一个子数组中每个数字的第N位都为1,而第二个子数组的每个数字的第N位都为0。

现在我们已经把原数组分成了两个子数组,每个子数组都包含一个只出现一次的数字,而其他数字都出现了两次。因此到此为止,所有的问题我们都已经解决。

基于上述思路,我们不难写出如下代码:

代码:

#ifndef_FIND2NUMAPPEARONCEINARRAY_H_

#define_FIND2NUMAPPEARONCEINARRAY_H_

 

#include<iostream>

 

// Find the indexof first bit which is 1 in num (assuming not 0)

intFindFirstBitIs1(int num)

{

    int indexBit=0;

    while(((num&1)==0)&&(indexBit<32))

    {

       num=num>>1;

       ++indexBit;

    }

    return indexBit;

}

 

// Is the indexBitbit of num 1?

bool IsBit1(intnum, unsigned int indexBit)

{

    num=num>>indexBit;

    return (num&1);

}

voidFindNumsAppearOnce(int data[], int length, int &num1, int &num2)

{

    if (length<2)

    {

       return;

    }

 

    //get num1^num2

    int resultExclusiveOR=0;

    for (int i=0;i<length;++i)

    {

       resultExclusiveOR^=data[i];

    }

 

    //get index of first bit, which is 1 inresultExclusiveOR

    unsigned intindexOf1=FindFirstBitIs1(resultExclusiveOR);

 

    num1=num2=0;

    for (int j=0;j<length;++j)

    {

       if (IsBit1(data[j],indexOf1))

       {

           num1^=data[j];

       }

       else

       {

           num2^=data[j];

       }

    }

}

 

 

int run()

{

    int data[]={1,3,3,7};

    int len=sizeof(data)/sizeof(int);

    int num1;

    int num2;

    FindNumsAppearOnce(data,len,num1,num2);

    printf("%d,%d",num1,num2);

    system("pause");

    return 0;

}

#endif

 

 

2.  数组中只出现一次的三个数字

分析:

题目:一个数组中有三个数字a、b、c只出现一次,其他数字都出现了两次。请找出三个只出现一次的数字。

分析:在博客http://zhedahht.blog.163.com/blog/static/2541117420071128950682/中我们讨论了如何在一个数组中找出两个只出现一次的数字。在这道题中,如果我们能够找出一个只出现一次的数字,剩下两个只出现一次的数字就很容易找出来了。

如果我们把数组中所有数字都异或起来,那最终的结果(记为x)就是a、b、c三个数字的异或结果(x=a^b^c)。其他出现了两次的数字在异或运算中相互抵消了。

我们可以证明异或的结果x不可能是a、b、c三个互不相同的数字中的任何一个。我们用反证法证明。假设x等于a、b、c中的某一个。比如x等于a,也就是a=a^b^c。因此b^c等于0,即b等于c。这与a、b、c是三个互不相同的三个数相矛盾。

由于x与a、b、c都各不相同,因此x^a、x^b、x^c都不等于0。

我们定义一个函数f(n),它的结果是保留数字n的二进制表示中的最后一位1,而把其他所有位都变成0。比如十进制6表示成二进制是0110,因此f(6)的结果为2(二进制为0010)。f(x^a)、f(x^b)、f(x^c)的结果均不等于0。

接着我们考虑f(x^a)^f(x^b)^f(x^c)的结果。由于对于非0的n,f(n)的结果的二进制表示中只有一个数位是1,因此f(x^a)^f(x^b)^f(x^c)的结果肯定不为0。这是因为对于任意三个非零的数i、j、k,f(i)^f(j)的结果要么为0,要么结果的二进制结果中有两个1。不管是那种情况,f(i)^f(j)都不可能等于f(k),因为f(k)不等于0,并且结果的二进制中只有一位是1。

于是f(x^a)^f(x^b)^f(x^c)的结果的二进制中至少有一位是1。假设最后一位是1的位是第m位。那么x^a、x^b、x^c的结果中,有一个或者三个数字的第m位是1。

接下来我们证明x^a、x^b、x^c的三个结果第m位不可能都是1。还是用反证法证明。如果x^a、x^b、x^c的第m位都是1,那么a、b、c三个数字的第m位和x的第m位都相反,因此a、b、c三个数字的第m位相同。如果a、b、c三个数字的第m位都是0,x=a^b^c结果的第m位是0。由于x和a两个数字的第m位都是0,x^a结果的第m位应该是0。同理可以证明x^b、x^c第m位都是0。这与我们的假设矛盾。如果a、b、c三个数字的第m位都是1,x=a^b^c结果的第m位是1。由于x和a两个数字的第m位都是1,x^a结果的第m位应该是0。同理可以证明x^b、x^c第m位都是0。这还是与我们的假设矛盾。

因此x^a、x^b、x^c三个数字中,只有一个数字的第m位是1。于是我们找到了能够区分a、b、c三个数字的标准。这三个数字中,只有一个数字满足这个标准,而另外两个数字不满足。一旦这个满足标准数字找出来之后,另外两个数字也就可以找出来了。

这种思路的C++代码如下:

代码:

#ifndef_FIND3NUMAPPEARONCEINARRAY_H_

#define_FIND3NUMAPPEARONCEINARRAY_H_

 

#include<iostream>

#include<vector>

 

using namespacestd;

 

int lastBitOf1(intnum)

{

    return num & ~(num - 1);

}

 

voidgetTwoUnique(vector<int>::iterator begin, vector<int>::iteratorend, vector<int>& unique)

{

    int xorResult=0;

    vector<int>::iterator iter=begin;

    for (;iter!=end;++iter)

    {

       xorResult^=*iter;

    }

 

    int diff=lastBitOf1(xorResult);

 

    int first=0;

    int second=0;

 

    vector<int>::iterator iter1=begin;

    for (;iter1!=end;++iter1)

    {

       if (diff&*iter1)

       {

           first^=*iter1;

       }

       else

       {

           second^=*iter1;

       }

    }

    unique.push_back(first);

    unique.push_back(second);

}

 

voidgetThreeUnique(vector<int>& numbers, vector<int>& unique)

{

    if (numbers.size()<3)

    {

       return;

    }

 

    int xorResult=0;

    vector<int>::iteratoriter=numbers.begin();

    for (;iter!=numbers.end();++iter)

    {

       xorResult^=*iter;

    }

 

    int flag=0;

    for(iter=numbers.begin();iter!=numbers.end();++iter)

    {

       flag^=lastBitOf1(xorResult^*iter);

    }

    flag=lastBitOf1(flag);

 

     //get the first unique number

    int first=0;

    for(iter=numbers.begin();iter!=numbers.end();++iter)

    {

       if (lastBitOf1(*iter ^ xorResult) ==flag)

       {

           first ^= *iter;

       }

    }

    unique.push_back(first);

 

    // move the first unique number to the endof array

    for(iter=numbers.begin();iter!=numbers.end();++iter)

    {

       if (*iter==first)

       {

           swap(*iter,*(numbers.end()-1));

           break;

       }

    }

 

     //get the second and third unique numbers

    getTwoUnique(numbers.begin(),numbers.end()-1,unique);

}

 

 

int run()

{

    vector<int> numbers;

    numbers.push_back(1);

    numbers.push_back(3);

    numbers.push_back(3);

    numbers.push_back(5);

    numbers.push_back(7);

    numbers.push_back(7);

    numbers.push_back(9);

    vector<int> unique;

    getThreeUnique(numbers,unique);

    for (vector<int>::iteratoriter=unique.begin();iter!=unique.end();++iter)

    {

       cout<<*iter<<endl;

    }

    numbers.clear();

    unique.clear();

    system("pause");

    return 0;

}

#endif

 

 

 

说明:关于第一个问题比较简单,经过分析还是容易理解,至于第二个问题理解起来,逻辑思维能力要求还是比较高的,希望本博文对你有所帮助

转载至:http://zhedahht.blog.163.com/blog/static/2541117420071128950682/

http://zhedahht.blog.163.com/blog/static/25411174201283084246412/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值