数组中只出现一次的数字

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

分析:看到这题,首先要明白,这是求两个数字。还要明白其要求,空间复杂度和时间复杂度。如果没有这些约束我们可以有很多方法解,如Hash、逐次遍历等。

        当常规方法无法满足要求,我们就可以使用一个神奇的工具:位运算。

        我们首先看看当只是一个数字是只出现一次怎么求解。

        异或运算有一个性质:任何一个数字异或自己都等于0。多个数字依次做异或运算,相同的数字会抵消。看下例:

        例如一个输入数组:{1,2,3,2,3}

        0001

        0010  异或得到:

        0011

        0011 异或得到:

        0000

        0010 异或得到:

        0010

        0011 异或得到:

        0001

        看到木有,数组里面相同的数字都抵消了。这里只是针对只有一个数字是只出现一次的。其他情况不成立。

        

       但是本题里有两个,直接这么做事不成立的,怎么办? 我们想到,用位整个数组运算以后得到的结果有什么作用呢?很明显,出现两次的数在位运算后抵消了,那么其实就是两个只出现一次的两个数做的位运算。例如:

      {1,2,2,3}

      0001

      0010 异或得到:

      0011

      0010 异或得到:

      0001

      0011 异或得到:

      0010      这个结果和  1^3=0010是一样了,验证了上面的理论。

      好了,有了这个有用的信息,那么我们就可以用这个结果中位为1来区分成两组数据,很显然这两组数据中都分别只包含一个只出现一次的数字(作的异或运算嘛,=1,说明它们是不同的。)。然后再用最开始讲到的方法去做就OK拉。代码贴上来:

    

bool Judge(int pData, int BitIndex)
{
    pData = pData >> BitIndex;
    return (pData & 1);
}
void FindOnlyOneNumbers(int pData[],int length, int &num1,int &num2)
{
    //1.异常检测
    if (pData == NULL || length < 2)
        return;

    //2.求出区分位
    int resultExclusive = 0;
    for (int i = 0; i < length; i++)
        resultExclusive ^= pData[i];      //假如只有一个数字是只出现一次,那么这个结果就是这个数字,如果两个的话,那么这个数字中位为1即是只出现一次的两个数的不同位。

    //3.接下来为了区分两者,我们寻找区分位最右边为1的位
    int indexCount = 0;
    while (resultExclusive&1==0&&indexCount<=8*sizeof(int))
    {
        resultExclusive >> 1;
        indexCount++;
    }

    //4.然后按照区分位的不同,将pData分成两组,这里没必要用两个数组保存,直接进行用位运算分别计算两个数就ok了
    int resultExclusive1 = 0, resultExclusive2 = 0;
    for (int i = 0; i < length; i++)
    {
        if (Judge(pData[i],indexCount))
            resultExclusive1 ^= pData[i];
        else
            resultExclusive2 ^= pData[i];
    }
    num1 = resultExclusive1;
    num2 = resultExclusive2;
}

        

原文地址:https://www.cnblogs.com/menghuizuotian/p/3828909.html