题目
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
题目解释,比如有数组nums={1,2,4,3,2,1,6,6},找出两个只出现了一次的数,则输出结果为 3,4。
退一步讲,如果一个数组中,只有一个数出现一次,其余的数都出现两次,寻找该数。该题如何做呢?
可以利用位运算中的异或。
位运算性质如下:
符号 | 描述 | 结果 |
---|---|---|
& | 与运算 | 对应的二进制为都为1,结果才为1 |
| | 或运算 | 只有两个数二进制位有一个为1,结果则为1 |
^ | 异或运算 | 两个数对应的二进制为不同则为1,相同则为0 |
~ | 取反运算 | 二进制位1,结果为0;二进制位0,结果为1 |
利用位运算可以得到如下两个结果:
- 任意一个数异或本身,得到的结果为0。
- 0异或任意一个数,得到的结果为该数。
如果我们可以将数组划分为两个子数组,每个子数组里面包含一个只出现一次的数字,然后针对每个子数组内部一次做异或运算,因为相同的数异或之后都变成0,最后每个数组都会留下一个数字,该数即为只出现一次的数。
现在的问题关键在于如何划分数组了?
还是将原数组依次异或,出现两次的数异或之后为0,之后整个数组将会留下两个只出现一次的数,由于这两个数不相同,异或的结果肯定不为0。
只需要找到最后异或的结果中最低位为1的数,并记录下最低位为1的下标,记录下标为index,现在以index位是否等于1,将整个数组划分为两个子数组,两个数组特点为:
- 第一个数组index位置的二位数等于1。
- 第二个数组index位置的二位数等于0。
因此如果两个数相同,则两个数一定在同一个子数组中,因为相同的数的第index位置一定是相同的,不可能将两个相同的数分配到两个不同的子数组中,并且每个子数组中都只包含一个出现一次的数。
然后只需要分别异或两个子数组,即可得到每个子数组中出现一次的数。
代码如下
public class Solution {
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
int length = array.length;
if(length == 2){
num1[0] = array[0];
num2[0] = array[1];
}
int bitResult = 0;
for(int i = 0;i < length; i++){
bitResult ^= array[i];
}
// 计算数中的最低位的1的下标
int index = getLowOne(bitResult);
// 将原数据进行分组
for(int i = 0; i < length; i++){
if(numIsOne(array[i],index)){
num1[0] ^= array[i];
}else{
num2[0] ^= array[i];
}
}
}
// 计算数组中最低位为1的下标
public int getLowOne(int num){
int index = 0;
while(((num & 1)==0 )&& index < 32){
num = num >> 1;
index ++;
}
return index;
}
// 判断某个数对应的下标是否是1
public boolean numIsOne(int num,int index){
if(((num>>index) & 1) == 1){
return true;
}
return false;
}
}
2. 总结
该题主要考察数的位运算,可以尝试由简变难的处理方式,先尝试处理数组中只有一个出现一次的数,再处理数组中有两个出现一次的数。