题目大意
每次给你 2 * n + 2 个的数字,除其中两个数字之外其他每个数字均出现两次,找到这两个数字并升序输出。
思路
最简答的想法,排序,然后找出只出现一次的数,不再叙述。
来说一说用位运算怎么解决:
假设这两个不同的数 x2 = 1100101 和 y2 = 1010001。
观察标记出来的位置。易知,对于两个不同的数,一定有一个位置 pos 对应这两个数在二进制下是不同的。 上边的例子中,pos = 3
我们这里先讲两个前导知识:
0 ^ x = x,x 异或 0 的结果还是 x
x ^ x = 0,两个相同的数异或结果为 0
根据这两个性质,我们可以在 2 * n + 1 个数中,轻易的找出落单的一个数。eg: 1 ^ 1 ^ 2 ^ 2 ^ 3 = 3
我们结合刚刚的结论,如何用前导知识来找出落单的两个数。
我们就刚刚的 x 和 y 来讲,x 对应的二进制数下第 3 位是 1,而 y 对应的二进制数下第 3 位是 0。
要是我们把这 2 * n + 2 中,二进制数下第 3 位是 1 的所有数都拿过来,就会变成这样:
x = 110 0101
a1 = 110 1101
a2 = 110 1101
a3 = 101 1101
a4 = 101 1101
很明显,x ^ a1 ^ a2 ^ a3 ^ a4 = x。
同样的对于二进制数下第 3 位是 0 的所有数:
y = 101 0001
a5 = 110 1001
a6 = 110 1001
a7 = 101 1001
a8 = 101 1001
y ^ a5 ^ a6 ^ a7 ^ a8 = y。
也就是,我们对 2 * n + 2 个数,依据在 pos 下的数值分组即可。
现在的问题就是,如何找到 x 和 y 对应的 pos。
这里再来一个前导知识,x & (x-1) 的结果为:x 减去其二进制下从右往左数的第一个 1.
eg: x = 11010,那么 x & (x-1) = 11000
如果我们让 x 减去 x &(x-1), 这时的结果为:x 对应二进制下第一个 1 表示的数
eg: x = 11010,x - (x & (x-1)) = 00010
ps:位运算的优先级是最低的,记得加括号。x - (x & (x-1)) = x & (x^(x-1)),有兴趣自己证明吧
异或运算是相同位为 0, 不同位为 1。
令 z = x ^ y = 1100101 ^ 1010001 = 110100。
观察 z 我们可以发现,z 在二进制下从右往左的第一个 1 就是 x 和 y 对应的 pos。
到这里这个问题就结果了,具体看代码吧:
代码
#include<stdio.h>
int a[10005];
int main(){
int n, i;
scanf("%d", &n);
n = n * 2 + 2;
for(i = 0; i < n; i++) scanf("%d", &a[i]);
int z = 0;
for(i = 0; i < n; i++) z ^= a[i];
// 对所有数异或,相同的数异或结果位 0,最终的z = x ^ y
int pos = z - (z & (z-1));
// 找到 z 在二进制下第一个 1,也就是 x 和 y 在二进制下第一个不同的位置
int x = 0;
for(i = 0; i < n; i++){
if((pos & a[i]) != 0){ // 找到所有的在 pos 这个位置为 1 的数
x ^= a[i];
}
}
// 解释一下 if 判断, 100 & 101 = 100, 100 & 001 = 0, 达到一个分组的目的
int y = z ^ x; // z = y ^ x, z ^ x = y ^ x ^ x = y
if(x > y) printf("%d %d\n", y, x);
else printf("%d %d\n", x, y);
return 0;
}