剑指Offer学习总结-数组中只出现一次的数字

剑指Offer学习总结-数组中只出现一次的数字

本系列为剑指Offer学习总结,主要是代码案例的分析和实现:
书籍链接:http://product.dangdang.com/24242724.html
原作者博客:http://zhedahht.blog.163.com/blog/static/254111742011101624433132/
原作者博客链接有完整的项目代码下载。


数组中只出现一次的数字

题目

题目:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
要求时间复杂度为O(n),空间复杂度为O(1)。
例如输入数组 {2,4,3,6,3,2,5,5}, 因为只有 4、 6 这两个数字只出现一次, 其他数字都出现了两次, 所以输出 4 和 6。

最初的解法

 我们之前已经做过,从数组中找出出现一次的数字类似题目。
 我们之前采用了哈希表的方法,利用辅助空间的解法。
 但是我们现在题目有要求不能使用辅助空间。

符合要求的解法

现在我们不能使用额外的辅助空间,所以,我们需要考虑其他的方式。
我们从简单来考虑,如果现在题目中只有一个出现一次的数字。
这里我们需要用到异或的运算,这里原书感觉讲解不是很到位,看了半天没有理解。
首先交待一下异或的基本性质:2个相同的数异或等于0,任何数与0异或都是本身,且异或操作(^)满足结合律和交换律。这里异或是重点,这里的满足结合律和交换律是重中之重。
异或:1^1=0,1^0=1

想到这里,我们应该可以理解了。
如果原数据是aabbccddeff 我们也可以任意打乱顺序。
如果只有一个出现一次的数据
我们异或的处理是a^a^b^b^c^c^d^d^e^f^f 相同的数据异或为0 所以最后的结果为e
如果有两个出现一次的数据
我们异或的处理是a^a^b^c^c^d^d^e^f^f 相同的数据异或为0 所以最后的结果为b^e

若把数组分成两个数组,每个数组包含一个只出现一次的数字,其它数字出现两次,然后分别在对两个数组用前面方法求异或

所以,如果我们能把原来问题中的数组,分成2个子数组,使得每个子数组中都只有一个唯一的元素以及很多成对的元素,那么我们就可以求出每个子数组中唯一的元素,最终就可以得到原数组中2个出现次数唯一的元素。
方法是这样的:
1. 首先数组中所有元素依次异或,因为相同的元素异或得到0,所以最终的答案就等于那2个唯一的元素a^b的值。
2. 因为a,b不同,所以异或得到的答案肯定是不等于0的,那么我们就找到a^b的二进制表示中第一个为1的位,假如是第k位。而a,b两个数在第k位上是不同的,一个为0,一个为1
3. 接下来我们将第k位是1的分成一组,第k位是0的分成一组,如果2个元素相同,那么他们第k位肯定是一样的,所以肯定被分到同一组中。而a,b则被分到2组中去了。
然后我们就可以在每个分组中异或每一个元素,最终就可以得到那2个唯一的元素。

例子:
比如说1,1,2,2,3,4,5,5
最后异或的结果就是3^4=1110
根据倒数第2位上是否为1,将原先的数组分为2组:
1,1,2,2,3
4,5,5
这样分别异或,第一组得到3,第二组得到4,完成

void FindNumsAppearOnce(int data[], int length, int* num1, int* num2)
{
    if (data == NULL || length < 2)
        return;

    int resultExclusiveOR = 0;
    for (int i = 0; i < length; ++ i)
        resultExclusiveOR ^= data[i];

    unsigned int indexOf1 = FindFirstBitIs1(resultExclusiveOR);    

    *num1 = *num2 = 0;
    for (int j = 0; j < length; ++ j)
    {
        //将两个不同的数字分到两个数组  注意这里已经进行了连续异或运算
        if(IsBit1(data[j], indexOf1))
            *num1 ^= data[j];
        else
            *num2 ^= data[j];
    }
}
// 找到num从右边数起第一个是1的位
unsigned int FindFirstBitIs1(int num)
{
    int indexBit = 0;
    //当前位不等于1 并且 小于int类型的二进制位数
    while (((num & 1) == 0) && (indexBit < 8 * sizeof(int)))
    {
        num = num >> 1;
        ++ indexBit;
    }
    return indexBit;
}
// 判断数字num的第indexBit位是不是1
bool IsBit1(int num, unsigned int indexBit)
{
    num = num >> indexBit;
    return (num & 1);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值