题目
一个整型数组 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]
限制:
2 <= nums.length <= 10000
解题思路
前置知识
异或
表示当两个数的二进制表示,进行异或运算时,当前位的二进制相同为0,不同为1.
表示为:
- 0 ^ 0 = 0
- 1 ^ 0 = 1
- 0 ^ 1 = 1
- 1 ^ 1 = 0
特点:
- 0异或任何数,是任何数;
- 1异或任何数,任何数取反;
- 任何一个数字异或自己都等于0
了解了上述知识后,我们来看一下这道题
1.题目要求我们找出这两个只出现一次的数字。我们首先想到的可能是去暴力求解,但是题目要求时间复杂度是O(n),空间复杂度是O(1)。所以我们不得不换一种思想,这个时候我们就可以去考虑一下用位运算去解决了。
2.我们知道如果在求一个数组中只出现一次的数字时,我们可以直接使用异或来求出这个单独的数字
举个例子:
异或后:
3.但是此时我们的一个数组中存在两个只出现一次的数字
再举个例子
异或后:
最终得到的结果就是两个只出现一次的数字的疑惑结果。因为其他数字都出现了两次,在异或中全部抵消了。
4.由于两个数字不一样,异或结果不为0,也就是说在这个结果数字的二进制中至少有一位是不同的。若两个数字的二进制位不同时,那么他们异或后的二进制位就为一,
如上图,因为5和6二进制位的第一位和第二位都不同,所以5 ^ 6 后的二进制位的第一位和第二位就为1,此时我们可以设置一个int 类型的 m = 1 让 m &(与)(5 ^ 6),目的是去找到5和6从右往左第一位不同的二进制位,
5.然后我们根据这位不同的二进制位把原数组中的数字分为两个子数组,第一个子数组中每个数字的第二位都是1,而第二个子数组中每个数字的第二位都是0。由于我们分组的标准是数字中的某一位是1还是0,那么出现了两次的数字一定会被分配到同一个子数组。这样每个子数组都包含一个只出现一次的数字,而其他数字都出现了两次。
6.最后我们对两个分好的数组进行异或,这时因为两个不同的数被分进两个数组中,所以两个数组异或后的结果就是这两个不同的数,我们new一个新数组,将这两个异或后的结果放入并返回即可。
代码实现
class Solution {
public int[] singleNumbers(int[] nums) {
int temp = 0;
for(int i = 0; i < nums.length; i++){
temp ^= nums[i];
}
int m = 1;
while( (m & temp) == 0){
m = m << 1;
}
int x = 0, y = 0;
for(int i = 0; i < nums.length; i++){
if((m & nums[i]) == 0){
x ^= nums[i];
}else{
y ^= nums[i];
}
}
return new int[]{x,y};
}
}
测试结果