題目:一個整型數組裏除了兩個數字以外,其餘的數字都出現了兩次。java
* 請些程序找出這兩個只出現一次的數字。要求時間複雜度爲O(n),空間複雜度爲O(1)面試
例如輸入數組{2,4,3,6,3,2,5,5},由於只有4,6這兩個數字只出現一次,其餘數字都出現了兩次,因此輸出4,6數組
這是一個比較難的題目,不多有人在面試的時候不須要提示一會兒想到最好的解決辦法。通常當應聘者想了幾分鐘那個後尚未思路,面試官會給出一些提示。.net
咱們想到異或運算的一個性質:任何一個數字異或它本身都等於0,也就是說,若是咱們從頭至尾依次異或數組中的每個數字,那麼最終的結果恰好是哪一個出現一次的數字,由於那些成對出現的兩次的數字都在異或中低消了。get
咱們試着把數組分紅兩個子數組,使得每一個子數組只包含一次出現一次的數字,而其餘數字都成對出現兩次。若是可以這樣拆分紅兩個數組,咱們就能夠按照前面的辦法分別找出兩個只出現一次的數字了。it
咱們仍是從頭至尾依次異或數組中的每個數字,那麼最終獲得的結果就 是兩個只出現一次的數字的異或的結果。由於其餘數字都出現兩次,在異或中所有抵消了。因爲這兩個數字確定不同,那麼異或的結果確定不爲0,也就是說在這 個結果數字的二進制表示中至少有一位爲1.咱們在結果數字中找到第一個爲1的位的位置,記爲第n位。如今咱們以第n位是否是1爲標準把原數組中的數字分紅 兩個子數組,第一個子數組中的每一個數字的第n位都是1,而第二個子數組中每一個數字的第n位都爲0.因爲咱們分組的標準是數字中的某一位是1仍是0,那麼出 現了兩次的數字確定被分配到同一個子數組中。由於兩個相同的數字的任意一位都是相同的,咱們不可能把兩個相同的數字分配到兩個子數組中去,因而咱們已經把 原數組分紅了兩個子數組,每一個子數組都包含了一個只出現一次的數字,而其餘數字都出現了兩次。咱們已經知道如何在數組中找出惟一一個只出現一次的數字,因 此到此位置全部的問題都解決了。class
舉個例子,加入咱們輸入的數字是 {2,4,3,6,3,2,5,5}。當咱們依次對數組中的每個數字作異或運算以後,獲得的結果用二進制表示爲0010.異或獲得的結果中的倒數第二位 是1,因而咱們根據數字的倒數第二位是否是1分爲兩個數組。第一個子數組{2,3,6,3,2}中全部的數字倒數第二位都是1,而第二個子數組 {4,5,5}中全部的數字的倒數第2位都是0,接下來只要分別對這兩個子數組求異或,就能找出第一個子數組中只出現一次的數字是6,而第二個子數組只出 現一次的數字是4.test
Java實現的代碼以下:import
package cglib;map
public class jiekou {
public void findNumsAppearOnce(int[] arr){
if(arr == null)
return;
int number = 0;
for(int i: arr)
{
System.out.println("number="+number);
System.out.println("i="+i);
number^=i;
System.out.println("與i異或後:number="+number);
}
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){
System.out.println("findFirstBitIs1:number="+number);
int indexBit = 0;
System.out.println("findFirstBitIs1:indexBit="+indexBit);
while((number & 1)== 0){
System.out.println("findFirstBitIs1:number & 1=0");
number = number >> 1;
System.out.println("findFirstBitIs1:number>> 1="+number);
++indexBit;
System.out.println("findFirstBitIs1:++indexBit="+indexBit);
}
return indexBit;
}
private boolean isBit1(int number,int index){
System.out.println("isBit1:number="+number);
System.out.println("isBit1:index="+index);
number = number >>index;
System.out.println("isBit1:number >>index="+number);
return (number & 1) == 0;
}
public static void main(String[] args){
int[] arr={6,2,4,3,3,2,5,5};
jiekou test = new jiekou();
test.findNumsAppearOnce(arr);
}
}
輸出:
number=0
i=6
與i異或後:number=6
number=6
i=2
與i異或後:number=4
number=4
i=4
與i異或後:number=0
number=0
i=3
與i異或後:number=3
number=3
i=3
與i異或後:number=0
number=0
i=2
與i異或後:number=2
number=2
i=5
與i異或後:number=7
number=7
i=5
與i異或後:number=2
findFirstBitIs1:number=2
findFirstBitIs1:indexBit=0
findFirstBitIs1:number & 1=0
findFirstBitIs1:number>> 1=1
findFirstBitIs1:++indexBit=1
isBit1:number=6
isBit1:index=1
isBit1:number >>index=3
isBit1:number=2
isBit1:index=1
isBit1:number >>index=1
isBit1:number=4
isBit1:index=1
isBit1:number >>index=2
isBit1:number=3
isBit1:index=1
isBit1:number >>index=1
isBit1:number=3
isBit1:index=1
isBit1:number >>index=1
isBit1:number=2
isBit1:index=1
isBit1:number >>index=1
isBit1:number=5
isBit1:index=1
isBit1:number >>index=2
isBit1:number=5
isBit1:index=1
isBit1:number >>index=2
4
6
拓展:
package cglib;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
public class jiekou {
/**
* 若是數組中沒有x,那麼數組中全部的數字都出現了3次,在二進制上,每位上1的個數確定也能被3整除。如{1, 5, 1, 5, 1, 5}從二進制上看有:
1:0001
5:0101
1:0001
5:0101
1:0001
5:0101
二進制第0位上有6個1,第2位上有3個1.第1位和第3位上都是0個1,每一位上的統計結果均可以被3整除。而再對該數組添加任何一個數,若是這個數在二進制的某位上爲1都將致使該位上1的個數不能被3整除。所以經過統計二進制上每位1的個數就能夠推斷出x在該位置上是0仍是1了,這樣就能計算出x了。
推廣一下,全部其餘數字出現N(N>=2)次,而一個數字出現1次均可以用這種解法來推導出這個出現1次的數字。
*
*
*/
/**
* 數組a中只有一個數出現一次,其餘數都出現了2次,找出這個數字
* @param a
* @return
*/
public static int find1From2(int[] a){
int len = a.length, res = 0;
for(int i = 0; i < len; i++){
res = res ^ a[i];
}
return res;
}
/**
* 數組a中只有一個數出現一次,其餘數字都出現了3次,找出這個數字
* @param a
* @return
*/
public static int find1From3(int[] a){
int[] bits = new int[32];
int len = a.length;
for(int i = 0; i < len; i++){
for(int j = 0; j < 32; j++){
bits[j] = bits[j] + ( (a[i]>>>j) & 1);
}
}
int res = 0;
for(int i = 0; i < 32; i++){
if(bits[i] % 3 !=0){
res = res | (1 << i);
}
}
return res;
}
/**
* 數組a中只有一個數出現2次,其餘數字都出現了4次,找出這個數字
* @param a
* @return
*/
public static void find1From6(int[] a){
Map<Integer,Integer> map=new HashMap<Integer,Integer>();
for(int i=0;i<a.length;i++){
Integer num=map.get(a[i]);
if(num!=null){
map.put(a[i],++num);
}else{
map.put(a[i],1);
}
}
int n=2; //設置輸出指定次數的值
Iterator<Entry<Integer,Integer>> ite=map.entrySet().iterator();
while(ite.hasNext()){
Entry<Integer,Integer> entry=ite.next();
if(entry.getValue() == n){
System.out.println("出現次數爲"+n+"次的數字有:"+entry.getKey());
}
}
}
/**
* 數組a中只有一個數出現一次,其餘數字都出現了5次,找出這個數字
* @param a
* @return
*/
public static int find1From5(int[] a){
int[] bits = new int[32];
int len = a.length;
for(int i = 0; i < len; i++){
for(int j = 0; j < 32; j++){
bits[j] = bits[j] + ( (a[i]>>>j) & 1);
}
}
int res = 0;
for(int i = 0; i < 32; i++){
if(bits[i] % 5 !=0){
res = res | (1 << i);
}
}
return res;
}
public static void main(String[] args){
int[] arr2={6,2,2,3,3,4,4};
int[] arr3={2,4,4,4,3,3,3};
int[] arr5={8,2,2,2,2,2,4,4,4,4,4};
int[] arr6={1,1,1,1,2,2,3,3,3,3,4,4};
System.out.println(find1From2(arr2));
System.out.println(find1From3(arr3));
System.out.println(find1From5(arr5));
find1From6(arr6);
}
}
輸出: 6 2 8 出現次數爲2次的數字有:2 出現次數爲2次的數字有:4