異或的應用 及劍指offer 面試 40 數組中只出現一次的數字

轉載請註明出處:http://blog.csdn.net/ns_code/article/details/27568975

    這篇文章沒有代碼,介紹的是純理論的思路。

    異或是一種基於二進制的位運算,用符號XOR或者 ^ 表示,其運算法則是對運算符兩側數的每一個二進制位,同值取0,異值取1。它與布爾運算的區別在於,當運算符兩側均爲1時,布爾運算的結果爲1,異或運算的結果爲0。

    異或的性質:

    1、交換律:a^b = b^a;

    2、結合律:(a^b)^c = a^(b^c);

    3、對於任意的a:a^a=0,a^0=a,a^(-1)=~a。

    瞭解了上面這些,來看看這個,很重要,後面的程序都要用到這個結論:

    對於任意的a,有a^b^c^d^a^k = b^c^d^k^(a^a) = b^c^d^k^0 = b^c^d^k,也就是說,如果有多個數異或,其中有重複的數,則無論這些重複的數是否相鄰,都可以根據異或的性質將其這些重複的數消去,具體來說,如果重複出現了偶數次,則異或後會全部消去,如果重複出現了奇數次,則異或後會保留一個。

    下面來看兩道題目:

     1、1-1000放在含有1001個元素的數組中,只有唯一的一個元素值重複,其它均只出現一次。每個數組元素只能訪問一次,設計一個算法,將它找出來;不用輔助存儲空間,能否設計一個算法實現?

    當然,這道題,可以用最直觀的方法來做,將所有的數加起來,減去1+2+3+...+1000的和,得到的即是重複的那個數,該方法很容易理解,而且效率很高,也不需要輔助空間,唯一的不足時,如果範圍不是1000,而是更大的數字,可能會發生溢出。

    我們考慮用異或操作來解決該問題。現在問題是要求重複的那個數字,我們姑且假設該數字式n吧,如果我們能想辦法把1-1000中除n以外的數字全部異或兩次,而數字n只異或一次,就可以把1-1000中出n以外的所有數字消去,這樣就只剩下n了。我們首先把所有的數字異或,記爲T,可以得到如下:

T = 1^2^3^4...^n...^n...^1000 = 1^2^3...^1000(結果中不含n)

    而後我們再讓T與1-1000之間的所有數字(僅包含一個n)異或,便可得到該重複數字n。如下所示:

T^(a^2^3^4...^n...^1000) = T^(T^n) = 0^n = n

    這道題到此爲止。

    2、一個數組中只有一個數字出現了一次,其他的全部出現了兩次,求出這個數字。

    明白了上面題目的推導過程,這個就很容易了,將數組中所有的元素全部異或,最後出現兩次的元素會全部被消去,而最後會得到該只出現一次的數字。

    該題目同樣可以該爲如下情景,思路是一樣的:數組中只有一個數字出現了奇數次,其他的都出現了偶數次。

題目描述:一個整型數組裏除了兩個數字之外,其他的數字都出現了兩次。請寫程序找出這兩個只出現一次的數字。輸入:

每個測試案例包括兩行:

第一行包含一個整數n,表示數組大小。2<=n <= 10^6。

第二行包含n個整數,表示數組元素,元素均爲int。

輸出:對應每個測試案例,輸出數組中只出現一次的兩個數。輸出的數字從小到大的順序。樣例輸入:

8
2 4 3 6 3 2 5 5

樣例輸出:

4 6

    思路:上篇博文中已經瞭解到異或去重的原理,而且知道如果只有一個只出現一次的數字的求法,但這裏是有兩個只出現一次的數字,我們便要想辦法把他分爲兩個子數組,每個子數組中包含一個只出現一次的數字,其他的數字都出現了兩次。劍指offer上的思路很巧妙,依然從頭到尾異或所有的數字,這樣得到的結果實際上就是兩個只出現了一次的數字異或的結果,我們在異或後的結果中找出其二進制中最右邊爲1的位,該位既然爲1,說明異或的兩個數字對應的該位肯定不同,必定一個爲1,一個爲0,因此我們可以考慮根據此位是否爲1來劃分這兩個子數組,這樣兩個只出現一次的數字就分開了,但我們還要保證出現兩次的數字都分到同一個子數組中,肯定不能兩個重複的數字分在兩個不同的子數組中,這樣得到的結果是不對的,很明顯,相同的數字相同的位上的值是相同的,要麼都爲1,要麼都爲0,因此我們同樣可以通過判斷該位是否爲1來將這些出現兩次的數字劃分到同一個子數組中,該位如果爲1,就分到一個子數組中,如果爲0,就分到另一個子數組中。這樣就能保證每個子數組中只有一個出現一次的數字,其他的數字都出現兩次,分別全部異或即可得到這兩個只出現一次的數字。時間複雜度爲O(n)。

    另外,所有元素異或後,在找出最右邊爲1的時,我用的比劍指offer上更簡潔的代碼,主要用到了下面的結論:

對於一個數字X,X&(-X)之後得到的數字,是把X中最右邊的1保留下來,其他位全部爲0。注意,這裏的-X是X的相反數,-X=~X+1,這裏的~X意思是對X所有位取反,不要將二者弄混了。

    下面是AC的代碼:

[cpp] view plaincopy

  1. #include<stdio.h>
  2. #include<stdbool.h>
  3. /*
  4. 返回num的最低位的1,其他各位都爲0
  5. */
  6. int FindFirstBit1(int num)  
  7. {  
  8. //二者與後得到的數,將num最右邊的1保留下來,其他位的全部置爲了0
  9. return num & (-num);  
  10. }  
  11. /*
  12. 判斷data中特定的位是否爲1,
  13. 這裏的要判斷的特定的位由res確定,
  14. res中只有一位爲1,其他位均爲0,由FindFirstBit1函數返回,
  15. 而data中要判斷的位便是res中這唯一的1所在的位
  16. */
  17. bool IsBit1(int data,int res)  
  18. {  
  19. return ((data&res)==0) ? false:true;  
  20. }  
  21. void FindNumsAppearOnce(int *arr,int len,int *num1,int *num2)  
  22. {  
  23. if(arr==NULL || len<2)  
  24. return;  
  25. int i;  
  26. int AllXOR = 0;  
  27. //全部異或
  28. for(i=0;i<len;i++)  
  29.         AllXOR ^= arr[i];  
  30. int res = FindFirstBit1(AllXOR);  
  31.     *num1 = *num2 = 0;  
  32. for(i=0;i<len;i++)  
  33.     {  
  34. if(IsBit1(arr[i],res))  
  35.             *num1 ^= arr[i];  
  36. else
  37.             *num2 ^= arr[i];  
  38.     }  
  39. }  
  40. int main()  
  41. {  
  42. static int arr[1000000];  
  43. int n;  
  44. while(scanf("%d",&n) != EOF)  
  45.     {  
  46. int i;  
  47. for(i=0;i<n;i++)  
  48.             scanf("%d",arr+i);  
  49. int num1,num2;  
  50.         FindNumsAppearOnce(arr,n,&num1,&num2);  
  51. if(num1 < num2)  
  52.             printf("%d %d\n",num1,num2);  
  53. else
  54.             printf("%d %d\n",num2,num1);  
  55.     }  
  56. return 0;  
  57. }  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章