轉載請註明出處:http://blog.csdn.net/mmc_maodun/article/details/27800577
題目:一個int數組中有三個數字a、b、c只出現一次,其他數字都出現了兩次。請找出三個只出現一次的數字。
上篇博文中我們求的是兩個只出現一次的數字,且時間複雜度爲O(n),這次是三個,可以同樣考慮將數組先分成兩個子數組,求出其中一個只出現一次的數字,而後再將另一個子數組分成兩個子數組,再分別求這兩個只出現一次的數字。何海濤的博客給的就是這種思路,並給出了詳細的證明過程,詳見:http://zhedahht.blog.163.com/blog/static/25411174201283084246412/,但該方法真的要自己去想,很難想到,而且如果是面試的話,很難給面試官講懂。下面介紹另外一種方法,該方法具一定的通用性,對於2個,3個出現一次的數字這類的問題,都可以按照該思路去求解,只是時間複雜度可能要稍微大些,爲O(8*sizeof(int)*n),8*sizeof(int)其實即使int的位,在一般的32位系統中,它爲32,而n自然就是數組的長度了。
該方法的思路如下:
首先由於有3個數字出現一次,其他的都出現兩次,所以n肯定爲奇數,該方法通過掃描整數的每一位來逐個判斷。
再看這3個只出現一次的數字,他們的bit位肯定不可能全部相同,也就是說,雖然有些bit位上的數可能相等,但肯定至少存在某一個bit位,這三個數中,有兩個數的該bit位爲1,一個數的該bit位爲0,或者兩個數的該bit位爲0,一個數的該bit位爲1。我們可以通過掃面int的所有bit位,掃描每個bit位的時候,遍歷數組,如果能找出符合上面條件的,我們就可以找出其中的一個只出現一次的數字,該數字與另外兩個只出現一次的數的bit位不同。找到一個之後,就可以將其與數組的最後一個元素交換,再在前面n-1個數中找出另外兩個就可以了,方法的話,可以直接用上篇博文中介紹的方法,也可以用該博文介紹的思路。
下面要來看下如果找出這個與另外兩個數的該bit位不同的數。
先看第一種情況,如果a,b,c三個數中,有兩個該bit位爲0,另一個爲1,我們遍歷數組,分別統計該數組元素中該bit位爲1和0的元素個數,分別設爲count1和count0,並同時將所有該bit位爲1的元素異或,所有該bit位爲0的元素異或,得到的結果分別設爲temp1和temp0。如果count1爲奇數,則可能有兩種情況,a,b,c三個數的該bit位全爲1,或者有兩個爲0,一個爲1,如果有temp0==0,則說明是前一種情況(a,b,c的該bit位全爲1的話,所有該bit位爲0的每個元素出現了兩次,因此異或後的結果爲0),此時沒法找出其中的一個數,則直接跳到下次循環,繼續判斷下一個bit位,如果temp0!=0,則說明是後一種情況(說明該比bt位爲0的元素異或後沒有完全抵消,則說明有一個元素是隻出現一次的),此時其中一個只出現一次的數字就是temp0(重複的元素異或後都抵消了)。
第二種情況,是兩個該bit位爲1,另一個爲0的情況,分析思路與上面的類似。
很明顯,這種掃描每個bit位來進行判斷的思路可以解決整個的這一類問題,自然也可以求出上篇博文中兩個只出現一次的數字。具體思路不再給出,主要的判斷依據,一般都是bit位爲1或0的數字的個數的奇偶,bit位爲1的元素異或,bit位爲0的元素異或後的結果是否爲0的判斷。
下面給出這道題目的完整代碼:
#include<stdio.h>
/*
通過掃面每一位,先找出一個只出現一次的數
*/
int FindOneNumAppearOnce(int *arr,int len)
{
int count1 = 0; //某一位上1的個數
int count0 = 0; //某一位上0的個數
int temp1 = 0; //某一位爲1的所有數相異或的結果
int temp0 = 0; //某一位爲0的所有數相異或的結果
int i,j;
for(i=0;i<8*sizeof(int);i++) //循環計算每一位的以上四個數據
{
count1 = count0 = temp1 = temp0 = 0;//每次計算下一位時清零
for(j=0;j<len;j++)
{
//每次向左移一位進行計算
if(arr[j] & (1<<i)) //該位爲1時
{
temp1 ^= arr[j];
count1++;
}
else
{
temp0 ^= arr[j];
count0++;
}
}
if(temp1 & 1) //某位上有奇數個1
{
if(temp0 == 0) //此時3個不同數的該位都爲1
continue;
else //此時3個不同數的該位有1個1,2個0
return temp1;
}
else //某位上有偶數個1
{
if(temp1 == 0) //此時3個不同數的該位都爲0
continue;
else //此時3個不同數的該位有1個0,2個1
return temp0;
}
}
}
/*
返回num的最低位的1,其他各位都爲0
*/
int FindFirstBit1(int num)
{
//二者與後得到的數,將num最右邊的1保留下來,其他位的全部置爲了0
return num & (-num);
}
/*
判斷data中特定的位是否爲1,
這裏的要判斷的特定的位由res確定,
res中只有一位爲1,其他位均爲0,由FindFirstBit1函數返回,
而data中要判斷的位便是res中這唯一的1所在的位
*/
bool IsBit1(int data,int res)
{
return ((data&res)==0) ? false:true;
}
void FindTwoNumsAppearOnce(int *arr,int len,int *num1,int *num2)
{
int i;
int AllXOR = 0;
//全部異或
for(i=0;i<len;i++)
AllXOR ^= arr[i];
int res = FindFirstBit1(AllXOR);
*num1 = *num2 = 0;
for(i=0;i<len;i++)
{
if(IsBit1(arr[i],res))
*num1 ^= arr[i];
else
*num2 ^= arr[i];
}
}
/*
交換兩個int變量
*/
void Swap(int *a,int *b)
{
if(*a != *b)
{
*a ^= *b;
*b ^= *a;
*a ^= *b;
}
}
/*
找出這三個只出現一次的數字
*/
void FindThreeNumsAppearOnce(int *arr,int len,int *num1,int *num2,int *num3)
{
if(arr==NULL || len<3)
return;
*num1 = FindOneNumAppearOnce(arr,len);
//找到第一個找出的數字,並與最後一個元素交換,便於接下來剩下的兩個數字
int i;
for(i=0;i<len;i++)
if(*num1 == arr[i])
break;
Swap(&arr[i],&arr[len-1]);
FindTwoNumsAppearOnce(arr,len-1,num2,num3);
}
int main()
{
static int arr[1000000];
int n;
while(scanf("%d",&n) != EOF)
{
int i;
for(i=0;i<n;i++)
scanf("%d",arr+i);
int num1,num2,num3;
FindThreeNumsAppearOnce(arr,n,&num1,&num2,&num3);
printf("%d %d %d\n",num1,num2,num3);
}
return 0;
}
測試結果: