問題:給一個很大的數組,裏面有兩個數只出現過一次,其他數都出現過兩次,把這兩個數找出來
原理:兩個相同的數進行異或,其結果爲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;
}