version1: 找出一個數組中一個只出現一次的數字,其他數字都出現兩次:將所有數字異或,得到的結果即為只出現一次的。
version2: 找出一個數組中兩個只出現一次的數字,其他數字都出現兩次:將所有數字異或,得到的結果即為x=a^b, index為x中第一個為1的位,則a 和b中第index為必然有一個為1,有一個為0。據此將所有數據分為兩組,一組的第index為都為1,另一組第index為都為0,第一組數字異或得到a,第二組數字異或得到b.
時間復雜度為o(n),空間復雜度為o(1)。
(判斷某一位是否為1:int result=a&(1<<i))若結果不為0,則a的第i位為1;否則第i位為1。
version3:找出一個數組中三個只出現一次的數字,其他數字都出現兩次:將所有數字異或,得到的結果為x=a^b^c。x必然與a,b,c都不相同,因此x^a,x^b,x^c都不為0.
定義函數f(n),作用是保留數字n的二進制表示中最后一位1,而其他位都變為0. 則f(x^a),f(x^b),f(x^c)的結果均不為0.
考慮f(x^a)^f(x^b)^f(x^c)的結果,它肯定不為0。因為對於三個非零的數i,j,k, f(i)^f(j)的結果要么為0,要么結果的二進制結果中有兩個是1,而f(k)的結果中只有一位是1,所以f(i)^f(j)^f(k)一定不為0。
因此f(x^a)^f(x^b)^f(x^c)至少有一位為1,假設最后一位是1的位為第 m 位。那么x^a, x^b, x^c的結果中,有一個或者三個數字的第m位是1.
下面證明不可能三個結果的第m位都是1. 反證法證明:若x^a,x^b, x^c的第m位都是1,則a,b,c的第m位都和x的第m位相反,那么a,b,c的第m位相同。若都為0,則x=a^b^c的第m為也未0,與x的第m位與a,b,c第m位相反矛盾;同樣若都為1,則x第m位為1,與假設矛盾。因此a,b,c中只有一個數字的第m位為1。 於是我們找到了區分a,b,c三個數字的標准。一旦將第m位為1的數找出來之后,另外兩個數字也可以找出來了。
version4: 一個數組中除了一個數字出現一次外,其他數字都出現了三次,找出只出現了一次的數字。
思路:若數組中沒有x,則其他所有數字都出現了三次,那么所有數字的二進制表示的每一位相加都應該可以被3整除,如果某一位不能,則表明x在這一位上是1。 這種解決方案可以擴展到其他所有數字都出現了N次的情形。
代碼:
// 【白話經典算法系列之十七】<span >數組中只出現一次的數</span>
// by MoreWindows( http://blog.csdn.net/MoreWindows )
// 歡迎關注http://weibo.com/morewindows
#include <stdio.h>
#include <string.h>
int FindNumber(int a[], int n)
{
int bits[32];
int i, j;
// 累加數組中所有數字的二進制位
memset(bits, 0, 32 * sizeof(int));
for (i = 0; i < n; i++)
for (j = 0; j < 32; j++)
bits[j] += ((a[i] >> j) & 1);
// 如果某位上的結果不能被整除,則肯定目標數字在這一位上為
int result = 0;
for (j = 0; j < 32; j++)
if (bits[j] % 3 != 0)
result += (1 << j);
return result;
}
int main()
{
printf(" 【白話經典算法系列之十七】數組中只出現一次的數\n");
printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n");
printf(" -- http://blog.csdn.net/morewindows/article/details/12684497 -- \n\n");
const int MAXN = 10;
int a[MAXN] = {2, 3, 1, 2, 3, 4, 1, 2, 3, 1};
printf("%d\n", FindNumber(a, MAXN));
return 0;
}
