找出数组中只出现一次的两个数
题目描述:输入一个整型数组,数组中只有两个数字只出现了一次,其他的数都出现了两次。要求找出那两个数字,对于输出结果的顺序没有要求,时间复杂度要求在线性时间且只使用常数空间
1. 用Map的解法
这种方法比较直接,但是不符合线性时间和常数空间的要求。遍历的时候把每个数出现的次数存储到一个map中,再遍历一遍这个map找到出现次数为1的数即可。
/**
* 6ms 19.17% 遍历存到map里
* @param nums
* @return
*/
public int[] singleNumberMap(int[] nums) {
Map<Integer, Integer> map = new HashMap<>();
for (int i : nums) {
if (!map.containsKey(i)) {
map.put(i, 1);
} else {
map.put(i, map.get(i) + 1);
}
}
int[] res = new int[2];
int index = 0;
for (Integer key : map.keySet()) {
if (map.get(key) == 1) {
res[index++] = key;
}
}
return res;
}
2. 正确的解法
用到了LeetCode 136 Single Number里用位运算的思想。
这里有两个需要先了解的公式A ^ 0 = A
和A ^ A = 0
/**
* 1ms 99.62%
* 位操作
* @param nums
* @return
*/
public int[] singleNumber(int[] nums) {
int[] res = new int[2];
int xor = nums[0];
// 因为a ^ a = 0 所以遍历整个数组后剩下的值是两个只出现一次的数A^B
for (int i = 1; i < nums.length; i++) {
xor ^= nums[i];
}
// 两个不同的数异或之后的二进制表示中 肯定至少存在一个1 xor &= -xor来找到最后的一个1
// 3(11) ^ 5(101) = 110
// 负数的二进制表示里是先对正数的二进制求反之后再+1 也就是用补码表示
// 110 & (10) = 010
xor &= -xor;
for (int i : nums) {
// i && xor == 0 说明i的后面位和xor一样
// 重复的数字因为成对出现 在做异或操作之后还是会变为0
// 任何数 ^ 0 = 本身
if ((i & xor) == 0) {
res[0] ^= i;
} else {
res[1] ^= i;
}
}
return res;
}