1. 程式人生 > >從陣列中找出只出現一次的兩個數,陣列中其他數都出現兩次

從陣列中找出只出現一次的兩個數,陣列中其他數都出現兩次

題目:在陣列中有兩個數只出現一次,其他數均出現兩次。問怎樣快速找出這兩個數。

方法一:

直接遍歷整個陣列,建成類似hash的陣列。用原始陣列中元素值當hash陣列下標,出現次數當hash陣列元素值。最後再遍歷一次hash,找出值為1元素的下標。或者不用hash陣列,用map的鍵值對。思想一樣的。

時間複雜度:O(n)

空間複雜度:O(n)

還有沒有更優的演算法呢?聯想到我們用位操作找出陣列中只出現一次的元素的思想,這裡同樣可以用位操作找出只出現一次的兩個元素。

方法二:(重要)

把兩個只出現一次的數記為a、b

1、將陣列中所有元素進行異或操作,因為相同的數異或為0,這樣得到的結果就是a異或b的值。

2、因為a和b肯定不相等,所以第一步得到的結果肯定不為0.也就是說此結果寫成二進位制至少有一位是1,找到這個為1的下標。用這一位我們可以把陣列中的數分成兩部分,一部分是這一位為1的數,一部分是這一位為0的數。a和b肯定不在同一個部分。陣列中原來相同的數肯定在同一個部分。

3、將這兩部分數分別進行異或運算。最後每部分異或的結果就是a和b。

時間複雜度:O(n)

空間複雜度:O(1)

#include<iostream>
#include<vector>
using namespace std;

vector<int> FindTwoOnce(vector<int> num){
    if(num.size()<=2)               //對符合要求的num陣列,只有兩個元素則直接返回
        return num;
    int res=0;                      //儲存陣列所有元素異或的結果
    for(int i=0;i<num.size();i++){
        res ^= num[i];
    }
    int index=0;                    //儲存異或結果二進位制表示中,從右往左第一個為1的下標
    for(int i=0;i<32;i++){
        int temp=res;
        if((temp>>i) & 1 == 1){     //第i位為1
            index=i;
            break;
        }
    }
    vector<int> result;             //儲存問題結果的陣列
    result.push_back(0);
    result.push_back(0);
    for(int i=0;i<num.size();i++){
        if((num[i]>>index) & 1 == 1)//第i位是1的一類
            result[0] ^= num[i];
        else                        //第i位是0的一類
            result[1] ^= num[i];
    }
    return result;
}

int main(){
    vector<int> num;
    num.push_back(2);
    num.push_back(2);
    num.push_back(3);
    num.push_back(4);
    num.push_back(5);
    num.push_back(5);
    vector<int> result=FindTwoOnce(num);
    cout<<result[0]<<' '<<result[1]<<endl;
    return 0;
}

繼續思考:

位操作可以在陣列中找出只出現一次的一個元素;只出現一次的兩個元素。那麼陣列中有三個元素只出現一次,其他元素都出現兩次,用位操作運算可以找出這三個數嗎?

答案是可以用位運算找出這三個數。

將陣列中所有的數進行異或運算,最後得到的是abc異或的結果。對此結果找到為1的那一位,用此位對陣列分成兩類。必有一類它的數字個數是奇數。這一類可能同時包含abc,也可能只包含abc其中一個。如果只包含abc其中一個,那麼問題就變成了陣列中有一個數只出現一次、陣列中有兩個數只出現一次,問題解決了。如果同時包含abc,那麼繼續上面的操作直到得到一部分只包含abc其中一個為止,問題也解決了。