劍指Offer(Java版):數組出現一次的數字

題目:一個整型數組裏除了兩個數字以外,其餘的數字都出現了兩次。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

相關文章
相關標籤/搜索