数组中只出现一次的(一个数、两个数、三个数)

关于位运算参考:http://blog.csdn.net/morewindows/article/details/7354571

关于位操作这里用到的三个要点:

1: 用位操作实现求一个数中的最后一个1

int lastbitof1(int number)

{

       return number&~(number-1)//数字减去1之后取反,然后与原来的数字做位与运算就求出了数字最后一个1

}

2: 用位操作实现求一个数中的第一个1

int  firstbitof1(int number)

{

      int indexbit=0;

while((num&1)==0&&(indexbit<8*sizeof(int)))

{

      number=number>>1;

      ++indexbit

}

      return indexbit;

}

3:    用位操作实现求一个数中有多少个1

把一个数减去1,再和原来的整数做与运算,会吧整数最右边的一个1变成0。那么一个整数中有多少个1,就会做多少次这样的运算。

int  numberof1(int number)

{

     int count=0;

      while(number)

       {

            ++count;

            number=(number-1)&number;

        }

       return count;

}

4   数组中只有一个整数出现了一次,其它整数都是出现了偶数次,求这个只出现了一次的整数

int FindNumAppearOnce(int * data,int length)

{

     int number=0;

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

            number^=data[i];

     return number;

}



题目:一个数组中有三个数字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的三个结果最后一位是1的是第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++代码如下:

void getThreeUnique(vector<int>& numbers, vector<int>& unique)

{

    if(numbers.size() < 3)

        return;

   

    int xorResult = 0;

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

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

        xorResult ^= *iter;

 

    int flags = 0;

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

        flags ^= lastBitOf1(xorResult ^ *iter);//这里最终的运算结果是f(x^a)^f(x^b)^f(x^c),即三个不同的数与xorresult异或后在三个一同异或的其情况,                                                   

    flags = lastBitOf1(flags);      //而根据上面的证明,假定最后一位是1的位是第m位,那么x^a、x^b、x^c中,位于m位上为1的只可能是有1个,所以根据这个第m位上是否为1                                                         // 可以找到第一个只出现一次的数,之后问题就转化为求数组中两个只出现一次的问题

   

    // get the first unique number

    int first = 0;

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

    {

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

            first ^= *iter;

    }

    unique.push_back(first);

   

    // move the first unique number to the end of 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 lastBitOf1(int number)

{

    return number & ~(number - 1);

}

 

void getTwoUnique(vector<int>::iterator begin, vector<int>::iterator end, vector<int>& unique)

{

    int xorResult = 0;

    for(vector<int>::iterator iter = begin; iter != end; ++iter)

        xorResult ^= *iter;

   

    int diff = lastBitOf1(xorResult);

   

    int first = 0;

    int second = 0;

   

    for(vector<int>::iterator iter = begin; iter != end; ++iter)

    {

        if(diff & *iter)

            first ^= *iter;

        else

            second ^= *iter;

    }

   

    unique.push_back(first);

    unique.push_back(second);

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值