剑指offer56
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例 1:
输入:nums = [4,1,4,6] 输出:[1,6] 或 [6,1]
示例 2:输入:nums = [1,2,10,4,1,4,3,3] 输出:[2,10] 或 [10,2]
此题可以使用两数异或的思想来区分相同的数和不同的数。
异或有一个鲜明的特点,它使得两个相同数异或结果为0,不同数结果一定不为0
在一个有多个数字的集合中,两两按序去异或操作,最后的结果就是剩下那两个数字异或的结果,因为其他两两都为0了
如同nums={1,2,10,4,1,4,3,3}一样,异或的结果为1000(二进制),十进制为8
得到这个数有什么用呢?
我们利用这个数将原本属于一个集合里的所有数字分成两组,将2和10一定能分开存放在不同的集合中,而其他两两相同的数字一定得放在同一个集合中;只有这样才能保证任一个集合里的数字全部异或后的结果就等于那个独立的数字,然后返回这两个集合各自异或的结果就好了
那么怎么去找到这样的一种方法分割两个集合呢?利用我们上次得到的异或结果1000(二进制)
我们从右往左看这个二进制数,第四位为0,表示2和10的最后一位一样,第三位为0,表示2和10的第三位一样…这样一直找下去,直到找到距离右边最近的一位1(当然不一定是最靠近右边的1,只是这样好找1罢了),这个位置记为x,这一位就可以将2和10分离,什么意思?就是让nums里的数字的第x位数字与1000的第x位数字(当然这里x是4)进行异或,这样相同的数字会被分到同一个集合,不相同的数字一定会被分到不同的集合
class Solution {
public int[] singleNumbers(int[] nums) {
int tem = 0;
//所有数字异或得到那两个数异或的结果
for(int num:nums){
tem ^= num;
}
//找到最小的位数为1的
int m = 1;
while((tem & m)==0){
m <<= 1;
}
int a=0;
int b=0;
for(int num:nums){
//分组与异或同时进行
if((num & m)==0){
a^=num;
}
else{
b^=num;
}
}
return new int[]{a,b};
}
}