劍指Offer面試題40(Java版):陣列出現一次的數字
* 請些程式找出這兩個只出現一次的數字。要求時間複雜度為O(n),空間複雜度為O(1)
例如輸入陣列{2,4,3,6,3,2,5,5},因為只有4,6這兩個數字只出現一次,其他數字都出現了兩次,所以輸出4,6
這是一個比較難的題目,很少有人在面試的時候不需要提示一下子想到最好的解決辦法。一般當應聘者想了幾分鐘那個後還沒有思路,面試官會給出一些提示。
我們想到異或運算的一個性質:任何一個數字異或它自己都等於0,也就是說,如果我們從頭到尾依次異或陣列中的每一個數字,那麼最終的結果剛好是哪個出現一次的數字,因為那些成對出現的兩次的數字都在異或中低消了。
我們試著把陣列分成兩個子陣列,使得每個子陣列只包含一次出現一次的數字,而其他數字都成對出現兩次。如果能夠這樣拆分成兩個陣列,我們就可以按照前面的辦法分別找出兩個只出現一次的數字了。
我們還是從頭到尾依次異或陣列中的每一個數字,那麼最終得到的結果就是兩個只出現一次的數字的異或的結果。因為其他數字都出現兩次,在異或中全部抵消了。由於這兩個數字肯定不一樣,那麼異或的結果肯定不為0,也就是說在這個結果數字的二進位制表示中至少有一位為1.我們在結果數字中找到第一個為1的位的位置,記為第n位。現在我們以第n位是不是1為標準把原陣列中的數字分成兩個子陣列,第一個子陣列中的每個數字的第n位都是1,而第二個子陣列中每個數字的第n位都為0.由於我們分組的標準是數字中的某一位是1還是0,那麼出現了兩次的數字肯定被分配到同一個子陣列中。因為兩個相同的數字的任意一位都是相同的,我們不可能把兩個相同的數字分配到兩個子陣列中去,於是我們已經把原陣列分成了兩個子陣列,每個子陣列都包含了一個只出現一次的數字,而其他數字都出現了兩次。我們已經知道如何在陣列中找出唯一一個只出現一次的數字,因此到此位置所有的問題都解決了。
舉個例子,加入我們輸入的數字是{2,4,3,6,3,2,5,5}。當我們依次對陣列中的每一個數字做異或運算之後,得到的結果用二進位制表示為0010.異或得到的結果中的倒數第二位是1,於是我們根據數字的倒數第二位是不是1分為兩個陣列。第一個子陣列{2,3,6,3,2}中所有的數字倒數第二位都是1,而第二個子陣列{4,5,5}中所有的數字的倒數第2位都是0,接下來只要分別對這兩個子陣列求異或,就能找出第一個子陣列中只出現一次的數字是6,而第二個子陣列只出現一次的數字是4.
Java實現的程式碼如下:
/** * 一個整型數組裡除了兩個數字之外,其他的數字都出現了兩次。 * 請些程式找出這兩個只出現一次的數字。要求時間複雜度為O(n),空間複雜度為O(1) */ package swordForOffer; /** * @author JInShuangQi * * 2015年8月10日 */ public class E40NumbersAppearOnce { public void findNumsAppearOnce(int[] arr){ if(arr == null) return; int number = 0; for(int i: arr) number^=i; int index = findFirstBitIs1(number); int number1= 0,number2 = 0; for(int i : arr){ if(isBit1(i,index)) number1^=i; else number2^=i; } System.out.println(number1); System.out.println(number2); } private int findFirstBitIs1(int number){ int indexBit = 0; while((number & 1)== 0){ number = number >> 1; ++indexBit; } return indexBit; } private boolean isBit1(int number,int index){ number = number >>index; return (number & 1) == 0; } public static void main(String[] args){ int[] arr={6,2,4,3,3,2,5,5}; E40NumbersAppearOnce test = new E40NumbersAppearOnce(); test.findNumsAppearOnce(arr); } }