1. 程式人生 > >《劍指Offer》面試題:找出陣列中有3個出現一次的數字

《劍指Offer》面試題:找出陣列中有3個出現一次的數字

題目

一個int陣列中有三個數字a、b、c只出現一次,其他數字都出現了兩次。請找出三個只出現一次的數字。

思路
由於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),即3個只出現一次的數均在第1類中,此時沒法找出其中的一個數,則直接跳到下次迴圈,繼續判斷下一個bit位,如果temp0不等於0,則說明是後一種情況(說明該比bit位為0的元素異或後沒有完全抵消,則說明在此類中有兩個數字只出現一次),此時其中一個只出現一次的數字就在另一類中,值就是temp1(重複的元素異或後都抵消了)。

同理:第二種情況也是類似的,如果 count0為奇數時,且temp1!=0時,則說明有兩個值出現了一次的數在bit=1的這類中,另一個則在bit=0的那一類中,且值為temp0;

將上面的思路在圖上顯示如下:

編碼實現如下:

/*
一個int陣列中有三個數字a、b、c只出現一次,其他數字都出現了兩次。請找出三個只出現一次的數字。
*/

/*
思路:按照上面提供的思路,現在3個次數值出現一次的數字中找出其中一個,然後利用上篇博文中找出兩個在陣列只出現一次的數字的方法。 
*/

#include<stdio.h>
#include<stdlib.h>
int xorArr(int *arr,int len){
    if(arr!=NULL&&len>0){
        int result=arr[0];
        for
(int i=1;i<len;i++){ result^=arr[i]; } return result; } } /* 返回a的最低位的1,其他各位都為0 */ int findFirstBit1Index(int a){ return a&(-a);// } /* 判斷a中特定的位是否為1,若特定的位為 1,則返回true; 這裡的要判斷的特定的位由b確定, b中只有一位為1,其他位均為0,由FindFirstBit1函式返回, 而a中要判斷的位便是b中這唯一的1所在的位是否為1 */ bool isBit1(int a,int b){ return a&b; } //函式功能:找出陣列中只有兩個只出現一次的數字。 void findTwoNumInArrayOnlyOnce(int *arr,int len,int *first,int *second){ if(arr==NULL||len<2){ return; } *first=0; *second=0; //第一步:將arr中所有的數異或。 int xorResult=xorArr(arr,len); //第二步:找到異或結果中最右邊為1的下標。 int index=findFirstBit1Index(xorResult); //第三步:根據index將arr分成兩個子陣列,每個子陣列中只有一個數字的次數為1,其餘都為兩次 for(int i=0;i<len;i++){ if(isBit1(arr[i],index)){//此子陣列中:為1 *first^=arr[i]; } else{ *second^=arr[i]; } } } //函式功能:檢測數字a在第i為是否為 1 bool isBit1In_i(int a,int i){ return a&(1<<i); //將1左移 i在進行與 } //函式功能:檢測數字a是否為奇數。 bool isOdd(int a){ return a%2; } void findThreeNumInArrayOnlyOnce(int *arr,int len,int *first,int *second,int *third){ if(arr==NULL||len<3){ return; } *first=0; *second=0; *third=0; //將全部資料為成兩類,第一類bit=1,第二類bit=0 int countBit=sizeof(int)*8;//每個int型別的整數所佔的bit數 int count1,count0;//分別用來儲存第一類和第零類中的元素個數。 int temp1,temp0;//分別用來儲存第一類和第零類中所有元素的異或結果。 for(int i=0;i<countBit;i++){ count1=count0=temp1=temp0;//每次迴圈時清零 for(int j=0;j<len;j++){ if(isBit1In_i(arr[j],i)){//檢測arr[j]在第i為是否為1. count1++; temp1^=arr[j]; } else{ count0++; temp0^=arr[j]; } } if(isOdd(count1)) {//count1為奇數時 即出現第一種情況:有兩個出現一次的數字該bit為 0,一個為1 的情況。或者全1 的情況。 if(temp0!=0){//則說明有兩個 出現一次的數字在bit=0的類,另一個在bit=1的類中,且值為temp1. ,否則什麼也不做。 *first=temp1; //找到一個數之後,,將此數放在陣列最後一個位置,然後在陣列的前n+1的元素中尋找。,然後呼叫在陣列中有兩個數字出現一次的函式即可解決問題。 arr[len]=temp1; findTwoNumInArrayOnlyOnce(arr,len+1,second,third); return;//返回即可。 } } else{//count1為偶數,即出現第二種情況:有兩個出現一次的數字該bit為 1,一個為0 的情況。 或者全0 的情況。 if(temp1!=0){//則說明有兩個 出現一次的數字在bit=1的類,另一個在bit=0的位,且值為temp0. ,否則什麼也不做。 *first=temp0; arr[len]=temp0; findTwoNumInArrayOnlyOnce(arr,len+1,second,third); return ;//返回即可。 } } } } int main(void){ int n; while(scanf("%d",&n)!=EOF&n>0){ int *arr=(int *)malloc((n+1)*sizeof(int));//多開闢一個空間,將找到的第一個數加入到最後一個位置,使得,這個數在陣列中出現兩次,進而方便尋找後面兩個只出現一次的數字。 if(arr==NULL){ exit(EXIT_FAILURE); } int val; for(int i=0;i<n;i++){ scanf("%d",&val); arr[i]=val; } int first,second,third; findThreeNumInArrayOnlyOnce(arr,n,&first,&second,&third); printf("%d %d %d\n",first,second,third); } return 0; }

小結
此題的思路確實比較巧妙,不太容易想的到。

本想放個執行後的截圖的,但是鍵盤的ctrl+v功能不行了,明天再弄上吧。
鍵盤上不知是ctrl+c還是ctrl+v有時候真心不靈,寫文件真心不方便呀,真心想換一個好鍵盤,哎。

剛說壞了,一會兒就好了,媽蛋