问题:给一个很大的数组,里面有两个数只出现过一次,其他数都出现过两次,把这两个数找出来
原理:两个相同的数进行异或,其结果为0,两个不相同的数急性异或,其结果不为0。
举例:99 ^ 99 = 1100011b ^ 1100011b = 0
95 ^ 99 = 1011111b ^ 1100011b = 0111100b= 60
在题目给出的很大的数组中,除要找的两个不同的数字外,其它的数字都是成对出现的,根据上面说到的两个相同的数字的异或其结果为0,因此,如果将整个数组中的元素进行异或,所得到的结果应该是所求的那两个不成对的数字的异或结果。假定数组中两个不同的数字是95 ^ 99,其异或的二进制结果是0111100b,其中有4位是1,这表明这两个数字的二进制有4位是不同的。(从右往左数) 它们分别是第3、4、5、6这4位,于是我们
只需要将数组中所有元素中第6位为1的元素和0111100b异或,
或者,将数组中所有元素中第5位为1的元素和0111100b异或,
或者,将数组中所有元素中第4位为1的元素和0111100b异或,
或者,将数组中所有元素中第3位为1的元素和0111100b异或。
就可以得到所求之两个数字中的一个数字,不妨以上面最后一条规则为例来进行说明:数组元素中第3位为1的数字,除所求的两个数字之外,都是成对出现的,它们所产生异或的结果肯定是0。而所求的那两个数字当中只有一个数字的第3位1,不妨假定这个数字是a(此时未知),另外一个要求的数字是b(此时未知),很明显,将a (此时未知)和0111100b异或就可以得到b (此时已知),再用0111100b和已经求出来的b (此时已知) 进行异或就可以得到a (此时已知)。比如上面的95的第3位为1,所以用95 ^ 0111100b = 1011111b ^ 0111100b = 1100011b = 99,再用99 ^ 0111100b = 1100011b ^ 0111100b = 1011111b = 95。
有了上面的解题思路,代码就好写了。参考代码如下:
#include <iostream>
using namespace std;
void get_2_numbers(int* arr, int size, int& number1, int& number2)
{
// 异或结果从右往左数第一个为1的bit的位置
int pos = 0;
// 异或结果
int exclusive_or = 0;
// 求出数组中各元素的异或
for(int i = 0; i < size; ++i)
{
exclusive_or ^= *(arr + i);
}
number1 = exclusive_or; // 临时保存异或结果
/*
while((exclusive_or & 1) == 0) // 如果最右一bit是0,则循环
{
++pos;
exclusive_or = exclusive_or >> 1;
}
// 循环结束时,得到的pos就是,异或结果中,从右往左数,第一个为1的bit的位置
*/
pos = exclusive_or & (-exclusive_or); // 根据kingbigeast的建议,用这一行代码代替上面的while循环
exclusive_or = number1;
for(int i = 0; i < size; ++i)
{
if((*(arr + i) >> pos) & 1)
{
number1 ^= *(arr + i);
}
}
number2 = number1 ^ exclusive_or;
return;
}
int main(int argc, char**)
{
// 要求的两个数字
int a, b;
// 下面数组模拟大数组中,有两个不同的元素95、99,其它元素皆成对出现
int arr[12] = {1,2,3,4,5,95,99,5,4,3,2,1};
get_2_numbers(arr, sizeof(arr) / sizeof(int), a, b);
cout << "所求的两个数字是:" << endl;
cout << a << "\t" << b << endl;
return 0;
}