【剑指offer】找出数组中只出现一次的2个数,其他数都是成对出现(出现一次的三个数)

时间:2022-10-31 19:13:56
 【题目描述】:

一个整形数组里除了两个数字外,其他的数字都出现了两次.请写程序找出这两个只出现一次的数字.

要求时间复杂度是On,空间复杂度为O1。

 【题目思路】:

首先对于这道题我们会先想到:如果一个数组中,只有一个数字出现一次,那么我们很容易想到异或用法:

异或: X^X = 0,X^0 = X。

所以会将数组中所有的数异或起来,那么最后结果就是出现一次的数。

想到这我们思路豁然开朗:

将两个出现一次的数分别放在两个不同数组,然后对每个数组的所有数进行异或运算,那么结果就显然易见了。

伴随着想法的产生,那么复杂的问题又来了:怎样将两个出现一次的数分到不同数组?如何确保成对出现的数能分到一个数组?

这分组依据如何确定?

经过深思熟虑,我们知道,将所有数字异或一起,最后结果就是只出现一次的数字异或的结果,然后因为两个数不同,异或结果肯定不为0,那么至少存在一个1,此时我们找到第一个1所在位置,并标记起来,记为第n位,然后按照第n位是否为1,将数组分为两个子数组,由于分配中是按照第n位是否为1的标准分的,那么相同的数肯定会在同一数组。那么依次异或两个子数组,结果就出来了。

例子:

比如说1,1,2,2,3,4,5,5

最后异或的结果就是3^4=1110

根据倒数第4位上是否为1,将原先的数组分为2组:

第一组:1,1,2,2,3

第二组:4,5,5

这样分别异或,第一组得到3,第二组得到4,即达到目的。

【代码如下】:

//找出只出现一次的两个数 
void NumbersAppearOnce(int data[],int length,int *num1,int *num2)  
{  
    if(data==NULL||length<2)  
        return;  
  
    //将整个数组的每个元素进行异或  
    int ResultExcusiveOr=0;  
    for(int i=0;i<length;++i)  
        ResultExcusiveOr^=data[i];  
    //整个数组异或结果,最右边为1的位置  
    unsigned int indexof1=FindFirst1(ResultExcusiveOr);  
  
    *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 FindFirst1(int num)  
{  
    int index=0;  
    while(((num&1)==0)&&(index<8*sizeof(int)))  
    {  
        num=num>>1;  
        ++index;  
    }  
    return index;  
}  
  
//检验num的第index个位置是否为1  
bool IsBit1(int num,unsigned int index)  
{  
    num=num>>index;  
    return (num&1);  
}   
当然此类问题相关题目:

题目一为给你1-1000个连续自然数,然后从中随机去掉两个,再打乱顺序,要求只遍历一次,求出被去掉的两个数

(基本跟上面的题一样);

题目二为:一个数组中有三个数字abc只出现一次,其他数字都出现了两次。请找出三个只出现一次的数字

题目详解--》: 一个数组中有三个数字 abc只出现一次,其他数字都出现了两次。请找出三个只出现一次的数字