1. 程式人生 > >陣列10:陣列中只出現一次的數字

陣列10:陣列中只出現一次的數字

題目:一個整型數組裡除了兩個數字之外,其他的數字都出現了兩次。請寫程式找出這兩個只出現一次的數字。

常識:異或運算

對於同一位,只要兩個值相同就為0,不同就為1,(與或運算不同,或運算只要有1就為1,沒有1才是0,即1或1等於1,0或0等於0;1或0等於1;0或1等於1),在計算機中,總是使用1代表true,0代表false, 1異或0等於1;1異或1等於0;0異或1等於1;0異或0等於1(即不同時表示百花齊放是1,相同時表示沒有特色是0)真^假=真;假^真=真;假^假=假;真^真=假;java中異或運算的符號是^

數字進行異或運算時首先轉換為相同位數的二進位制數,然後每一位對應進行異或運算,相同得到0,不同得到1,最後在轉化為對應的十進位制數。使用異或運算的一個典型應用是兩個相同的數進行異或運算一定得到0,即對於任何數a,有a異或a等於0;同時異或運算具有傳遞性,a^b^c=a^c^b(從它們的本質即位運算上可以很容易的理解)

於是有一個定理:兩個數異或運算等於0說明這裡兩個數一定相同;兩個數相同則它們異或運算一定得到0

使用這個定理有一些應用:對於一個數組,如果裡面元素兩兩相同,只有一個數字不同,那麼將所有數字作異或運算,由於相同的元素結果都為0,於是最終剩下的結果就是那麼單獨的元素值。---用於找出兩兩相同陣列中唯一單獨出現的數值。

本題中就可以使用異或運算的這個特性,如果一個數組中數值兩兩相同,只有一個數值唯一,那麼對這個陣列中的所有元素進行異或運算得到的結果就是這個單獨的值。由於本題中有兩個單獨的數,因此直接對整個陣列進行異或運算或得到一個非0的值,但是無法得出這兩個值是什麼,因此,需要先將整個陣列進行拆分,將它分成兩個陣列,使得2個單獨的元素分別出現在兩個陣列中,於是對每個陣列使用異或運算就可以得到兩個單獨的值。

如何拆分陣列:{2,4,3,6,3,2,5,5}

詳見劍指offer:先對整個陣列進行異或運算,如果為0則沒有兩個不同的數字,說明陣列中全部元素都成對出現(題目不會出現這種情況);一半異或結果得到一個不為0的數num,根據num二進位制的右側第一個1所在位數是否為1可以將陣列分成兩個子陣列(例如num=2其二進位制位0010,其右側倒數第二個1一定是由於兩個單獨數字a,b在倒數第二位一個為1,一個為0造成的),因此求出num要右移多少次count才會到這個位置,然後對陣列中每個元素都右移count次,看其是否為1(通過與1:0001作&運算看結果是否為0可以判斷),從而將陣列分成兩個部分(分別放入到方法中傳入的兩個陣列中),這裡要注意在堆num向右側進行移位時不要超出num本身具有的位數(當num為0時可能一直移位導致溢位,但是由於已經排除num為0,因此實際上並不會溢位)

注意幾個細節:①二進位制的邏輯運算與、或、非、異或等運算的優先順序很低,比==比較運算的優先順序還要低,因此需要加上括號(num&flag)==0

②巧妙使用flag=1的移位運算來檢驗num各位的值是否為1或者0

//num1,num2分別為長度為1的陣列。傳出引數

//num1[0],num2[0]設定為返回結果

//要求時間複雜度為O(n),空間複雜度為O(1),本題使用位運算中的異或運算的特性,是異或運算的典型應用

public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        //特殊輸入檢驗;空輸入或者輸入不滿足條件時直接結束方法,不用返回
        if(array==null||array.length<2) return;
        //對整個陣列進行異或運算
        int num=0;
        for(int i=0;i<array.length;i++){
            num^=array[i];
        }
        //如果num==0表示陣列中全部元素都是成對出現,不符合題意
        if(num==0) return;
        /*巧妙處理:找出num二進位制中右側第一個為1的位置(轉化為找到一個最小的flag,使得flag與num作&運算不為0)即不斷調整比較的位置flag=0001;flag=0010;flag=0100*/
        int flag=1;
        while((num&flag)==0){
            flag=(flag<<1);
        }
        //此時flag中1所在的位置m就是num中右側第一個1所在的位置,通過使用flag就不用記錄num右移的count,其實也一樣
        //將陣列分成兩個子陣列,分別進行異或運算(這裡子陣列並沒有創建出來,因為要求不能佔用空間複雜度)
        num1[0]=0;
        num2[0]=0;
        for(int i=0;i<array.length;i++){
            if((array[i]&flag)==0){       //&運算優先順序很低,要加上括號
                //說明這個元素在m位置為0,將其放入第一個子陣列
                num1[0]^=array[i];
            }else{ 
                //說明這個元素在m位置為1,將其放入到第二個子陣列
                num2[0]^=array[i];
            }
        }
    }
}