数组中只出现一次的数字

Posted by DH on July 19, 2017

题目

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

分析

这个题有一个要求就是时间复杂度是O(n),空间复杂度是O(1)。

如果没有这个要求,我们可以使用hashmap实现,循环一次,记录值和出现的次数,第二次循环找出出现一次的两个数。

既然这个方法行不通,就只有用新的方法。

磁盘里的存储都是成对出现的,现在磁盘损坏,丢失了一个数字,求这个数字(2,3,4,3,4)。

这个题目的解法使用的是位移操作,让数组中的数一次进行异或操作,成对出现的异或之后是0,0与落单的那个数异或为原来的数。

那如果一个数组中有两个数不一样,并且只出现一次呢?

例如,[2,3,4,3,4,5],3和4成对出现之后,异或相抵消,最后只剩下2和5相异或。

那么,假如我们能够将数组进行分组,一个分组中分别包含一个落单的数。

我们就可以对这个数组采用寻找磁盘损坏数字的方法进行求解。

那么要如何分组呢?

我们就找到两个落单的数相异或之后的结果的最低位位1的位置。

两个落单的数,这个位一定是不一样的,相同的数这个位置一定是一样的,那么久成功的将两个落单的数分开了。

然后把这两个组按照最开始的思路,依次异或,剩余的两个结果就是这两个只出现一次的数字。

代码

//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        if(array == null || array.length < 2)
        {
            return;
        }
        
        // 如果数组只有两个元素,这两个元素一定是不一样的,直接按照题目要求存入数组
        if(array.length == 2){
            num1[0] = array[0];
            num2[0] = array[1];
            return;
        }
        
        // 遍历整个数组,将所有元素依次异或,最终的结果就是两个不相同的元素异或的结果,因为想用的元素异或之后是0
        int result = 0;
        for(int i =0;i<array.length;i++){
            result ^= array[i];
        }
        
        //从右往左,找到异或结果第一位是1的位数
        int index = 0;
        while((result & 1) == 0 && index < 32){
		index ++;
            	result >>= 1;
        }
        
        // 按照这一位将原始数组分为两个数组,这样两个不同的数就分别在不同的数组了。然后遍历这两个数组,将每一个元素进行
        // 异或操作,最后剩下的就是不同的这两个数
        for(int j = 0; j < array.length; j++){
            if(((array[j] >> index) & 1) == 0){
                num1[0] ^= array[j];
            }else{
                num2[0] ^= array[j];
            }
        }
    }
}