這裏分享一道leetcode裏的算法題目,鏈接leetcode
這個題目很巧妙的利用了位運算。
一個整型數組 nums 裏除兩個數字之外,其他數字都出現了兩次。請寫程序找出這兩個只出現一次的數字。要求時間複雜度是O(n),空間複雜度是O(1)。
示例 1:
輸入:nums = [4,1,4,6]
輸出:[1,6] 或 [6,1]
示例 2:
輸入:nums = [1,2,10,4,1,4,3,3]
輸出:[2,10] 或 [10,2]
如果用容器,比如HashMap,那空間複雜度不可能是O(1)。看用位運算是如何解決這個問題的。
java異或運算(^)
異或的運算方法,是二進制運算。
1^1 = 0 ,
0^0 = 0,
1^0 = 1,
0^1 = 1
簡單一句話,相同爲0,不同爲1
舉個例子, 4^3
4的二進制:100
6的二進制:110
然後看4^6 :010 轉化成十進制就是2
異或運算法則
1. a ^ b = b ^ a
2. a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c;
3. d = a ^ b ^ c 可以推出 a = d ^ b ^ c.
4. a ^ b ^ a = b
很顯然:
a^a = 0,即與自身異或,得0;
a^0 = a,即與0異或,得自身
回到本文的那個算法題目,如果是,數組裏,僅有一個出現了一次,其它都出現了兩次,那將數組裏所有的數字異或,最後的結果就是那個出現一次的。
比如[1,2,3,4,3,2,1] 那麼:1^2^3^4^3^2^1 = 1^1^2^2^3^3^4 = 4 因爲a^a = 0 a^0 = a
如果這個理解的話,這個算法題目就可以入手了。把數組分成兩部分,每部分中都滿足:僅有一個數字出現一次,其它出現兩次。那就可以找到這兩個數字了。
怎麼分成兩部分呢? 假設數組nums中,a和b僅出現了一次。
nums中所有數字異或結果=a^b
假如 a^b = 2(二進制10),即二進制低二位上,a和b不一樣。那麼 a&2 和 b&2 一定不相等。這樣就把a和b分開了。 ----即二進制位中,找一個是1的。
nums中,其它的數字,同樣和2做相與操作(x&2),即可分成兩部分。
文字敘述不好理解,咱們上代碼
public int[] singleNumbers(int[] nums) {
if(null == nums){
return null;
}
int bit = 0; // 所有數字異或的結果
for(int num : nums){
bit ^= num;
}
int last = 1; // 獲取異或結果中低位1
while((last & bit) == 0){
last <<= 1;
}
int a = 0;
int b = 0;
for(int num : nums){
// 分成兩組(a和b如何分,上文說過了。同一個數字,相與num,結果一定相同,即一定分到同一組)
if((last & num) == 0){
a ^= num;
}else{
b ^= num;
}
}
return new int[]{a,b};
}