题目
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
分析
这个题有一个要求就是时间复杂度是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];
}
}
}
}