一、题目
一个整型数组 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
二、解决
1、异或位运算
思路:
简单版:除了一个数字,其他数字都出现了两次,那如何找到出现一次的数字?
解决:全员异或。相同的数字,异或一下会两两抵消。
进阶版:如何找出两个出现一次的数字?
解决-关键:分组
- 两个只出现一次的数字在不同的组中;
- 相同的数字会被分到相同的组中。
解决:对这两个组分别进行异或操作即可得到答案的两个数字。
思考:如何分组呢?
- 重复的数字进行分组。简单,可以用奇偶性,数值相同的一定会分到同一组。
- 难点:如何对两个不同数字进行分组。
- 方案:可以用 & 1 操作。也相当于奇偶性分组(不过这里不一定是最后一位,而是全员异或后,倒数第一个二进制为1),即判断最后一位二进制是否为1,判断数记为mask。
举例:
例子1:
4 ^ 1 ^ 4 ^ 6-->
4:0100
1:0001
-------
= 0101
4:0100
-------
= 0001
6:0110
-------
= 0111
1^6-->
6:0110
1:0001
-------
= 0111
0001可以用来分组。
mask:0001
例子2:
4 ^ 8 ^ 4 ^ 6-->
4:0100
8:1000
-------
= 1100
4:0100
-------
= 1000
6:0110
-------
= 1110
8^6-->
6:0110
8:1000
-------
= 1110
0010可以用来分组。
mask:0010
代码:
class Solution {
public int[] singleNumbers(int[] nums) {
//用于将所有的数异或起来
int k = 0;
for(int num: nums) {
k ^= num;
}
//获得k中最低位的1
int mask = 1;
//mask = k & (-k) 这种方法也可以得到mask,具体原因百度 哈哈哈哈哈
while((k & mask) == 0) {
mask <<= 1;
}
int a = 0;
int b = 0;
for(int num: nums) {
if((num & mask) == 0) {
a ^= num;
} else {
b ^= num;
}
}
return new int[]{a, b};
}
}
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
// k & (-k)
// 原码、反码、补码
// 正数:三码相同
// 负数
// 举例:-5
// 原码:1..101 (符号位置1)
// 反码:1..010 (符号位不变,其他取反)
// 补码:1..011 (反码+1)
System.out.println(Integer.toBinaryString(5)+" " );
System.out.println(Integer.toBinaryString(-5)+" ");
System.out.println(Integer.toBinaryString(5^(-5)));
00000000000000000000000000000101
11111111111111111111111111111011
11111111111111111111111111111110