劍指 Offer 56 - I. 數組中數字出現的次數
一個整型數組 nums 裏除兩個數字之外,其他數字都出現了兩次。請寫程序找出這兩個只出現一次的數字。要求時間複雜度是O(n),空間複雜度是O(1)。
解題思路
我們先從這個問題的簡化版本開始:一個整型數組 nums 裏除一個數字之外,其他數字都出現了兩次。
要解決這個問題,我們需要熟悉位運算符異或的功能:兩個bit A和B做異或,若A與B相同,則返回0;否則返回1
即
0 ^ 0 = 0;
0 ^ 1 = 1;
1 ^ 0 = 1;
1 ^ 1 = 0;
知道這一點後,我們可以容易地解決上述問題:只需將數組nums內的所有數字都做異或運算,最後得到的結果就是那個只出現一次的數字;
具體來說,比如有數組 1, 2, 1, 3, 4, 4, 3
我們做異或
1 ^ 2 ^ 1 ^ 3 ^ 4 ^ 4 ^ 3
= (1 ^ 1) ^ (3 ^ 3) ^ (4 ^ 4) ^ 2
= (0 ^ 0 ^ 0) ^ 2
= 2
回到我們原來的問題,現在給定的數組是 1, 2, 1, 3, 4, 4, 3 , 5; 多了一個只出現一次的數字5,現在有兩個只出現一次的數字2 和 5,其餘數字都出現兩次;
如何把問題簡化成我們上面說的只有一個數字只出現一次,使得我們能夠方便地用異或解決?——可不可以把這兩個只出現一次的數字分在兩個組裏?換句話說,我們能不能規定一個分組方法,把原數組分割成(1,2,1) 和 (3,4,4,3,5), 這樣我們就能對這兩個組分別運用上述的異或方法來找出只出現一次的數字;
如果我們按照元素的奇偶性分組,那麼可以分成兩組如下: (1,1,3,3,5) 和(2,4,4);這樣的兩組分組是我們所期望的,但是如果兩個出現一次的數字的奇偶性相同時,這個分組方法就失效了。
所以我們必須使用一種能夠確保這兩個出現一次的數字分別被分在兩個組內的分組方法;這裏給出一種分組方法:
對這兩個只出現一次的數字異或:
2 ^ 5
= 0010 ^ 1010
= 1001
從這個異或結果中得到的信息是:這兩個只出現一次的數字的最低位和最高位是不相等的(因爲異或結果中的對應位等於1);
所以我們可以根據這個結果來進行分組:把最低位 = 0的數組元素分爲一組, 最低位=1的數組元素分爲另一組,這樣就能夠把兩個只出現一次的數組元素分到兩個不相交的組,得到符合要求的分組;
對於那些出現兩次的數組元素,我們不需要爲他們額外考慮,因爲按照我們的分組方法,兩個相等的數組元素必定會被分配到同一組中去;
代碼實現:
class Solution {
public:
vector<int> singleNumbers(vector<int>& nums) {
int A_exclusiveor_B = 0; // A異或B的結果
for (int num : nums)
A_exclusiveor_B ^= num;
int Inequal_Bit = 1; // A和B不相等的那一位;這是我們可以從A異或B的結果中得到的信息
while ((Inequal_Bit & A_exclusiveor_B) != Inequal_Bit)
Inequal_Bit = Inequal_Bit << 1;
int A = 0, B = 0;
for (int num : nums) { // 分組異或
if (num & Inequal_Bit)
A ^= num;
else
B ^= num;
}
return vector<int> {A,B};
}
};