從陣列中找出只出現一次的兩個數,陣列中其他數都出現兩次
題目:在陣列中有兩個數只出現一次,其他數均出現兩次。問怎樣快速找出這兩個數。
方法一:
直接遍歷整個陣列,建成類似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其中一個為止,問題也解決了。