1. 程式人生 > >劍指Offer面試題40(Java版):陣列出現一次的數字

劍指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);
	}
}