原始题目:剑指 Offer 56 - I. 数组中数字出现的次数 (opens new window)
解题思路:
异或的定义:如果两个值相同,异或结果为 ,否则为 。
异或有两个重要性质
因此可以有
回到题目,将数组中相同的数字进行异或,则可以消除掉所有出现两次的数字,最后会只剩下两个只出现一次的数字 、 的异或结果 。
而 的二进制中为 1 的地方,就是 和 不同的地方。可以找到 中第一个为 的地方,假设为 。
那么可以将整个数组分为两部分,一部分是 为 的数字,一部分是 为 的数字。而且这两个只出现一次的数字会分别划分到这两个部分中。
如何快速找到 的二进制中第一个为 的地方(从右到左)?
通过 & ~ 这个运算过程,可以得到 中第一个 的位置。
假设 ,则 取反后为 。此时 & ~, ,则 & ~。
代码:
public int[] singleNumbers(int[] nums) {
if (nums == null || nums.length == 0 || nums.length % 2 == 1) {
return new int[0];
}
int xor = 0;
for (int num : nums) {
xor ^= num;
}
int m = xor & (~xor + 1);
int x = 0, y = 0;
for (int num : nums) {
// 分组异或
if ((num & m) != 0) {
x ^= num;
} else {
y ^= num;
}
}
// 其中没有两个不同的数字
if (x == 0 && x == y) {
return new int[0];
}
return new int[]{x, y};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
复杂度分析
- 时间复杂度:线性遍历 使用 时间。
- 空间复杂度:辅助变量占用常数大小的额外空间。