Single Number
這一系列有三道題,第一題也是最簡單最經典的。
有一個數組,里面的元素每個都出現了兩次,除了一個特殊的,求這個特殊元素。接觸過這類題目的coder很快能夠脫口而出:直接異或就ok了!的確如此:
var singleNumber = function(nums) {
return nums.reduce(function(pre, item) {
return pre ^ item;
});
};
但是為何這樣能得到答案?我們假設有個數組[1, 2, 3, 1, 2],很顯然我們要找出3這個元素。我們首先將數組元素全部用二進制表示:
0 1
1 0
1 1
0 1
1 0
我們從右往左,按位分析。如果數組中所有元素全部都是出現兩次,那么每一位上的1的數量之和肯定是2的倍數。我們看從右往左的第一位,1出現了三次,這多出的一次就是3的起作用,從右往左數第二位,1同樣出現了三次,同樣是因為3的原因。所以我們可以得到該元素的二進制表示為11,也就是3。
繼續思考,我們似乎需要這么一種“加法”運算,使得每位上的 1 bit 數量能夠得到累積,但是累積到2了就能清零。幸運的是,^運算符就是我們要找的。
於是代碼也就很好理解了。
Single Number II###
這是上題的加強版,數組中每個元素都出現三次,除了一個特殊的,找出這個特殊元素。
我們以數組[1, 2, 3, 1, 2, 1, 2]舉例,將數組元素用二進制表示:
0 1
1 0
1 1
0 1
1 0
0 1
1 0
如果理解了上題,此題的思路似乎也就呼之欲出了。我們也可以按位運算,計算1 bit的數量,如果每個數字都出現三次,那么每位上的1 bit數量肯定是3的倍數,相反如果不是3的倍數,那么就是那個特殊的數在搗蛋。
我們似乎需要這么一種“加法”運算,使得每位上的 1 bit 數量能夠得到累計,並且累計到了3就自動清零。但是理想是美好的,現實是殘酷的,並沒有這樣一種神奇的運算(三進制?)。
但是我們可以用一個數“輔助”,因為每一位的1 bit數量統計都是類似的,所以假設正在統計某一位的1 bit數量。我們用a來表示 1 bit 的數量,當 1 bit 的數量為0時,a=0;當數量為1時,a=1;當數量為2時,a=2?非也,位運算只能表示0和1,所以這時我們引進第二個變量b,我們用b=1來代表已經有了2個 1 bit,所以當有兩個 1 bit 時,a=0,b=1。數量統計結果逢3化0,所以只有0、1、2三種結果:
bits數量 a b
0 0 0
1 1 0
2 0 1
思路也就顯而易見了,每次運算我們維護a和b的值,運算到最后即可得到結果:
var singleNumber = function(nums) {
var a = 0, b = 0;
nums.forEach(function(item) {
b = a & (b ^ item);
a = b | (a ^ item);
});
return a;
};
當然最朴素的做法是按位枚舉每一位的 1 bit 的數量。
Single Number III
還是一個數組,每個元素出現兩次,只有兩個特殊的元素出現一次,把這兩個特殊的元素找出來。
兩個特殊的元素?這時候直接異或也並沒有什么卵用了啊...以數組[1, 1, 2, 2, 3, 3, 4, 5]舉例,如何把4和5找出來?一個數組中有兩個特殊的數字,不能用異或運算得到結果,如果只有一個了呢?沒錯,我們可以把4和5根據某一規則分到兩個數組中,然后在各自數組中進行異或從而得到結果。
那么如何分配?我們把4和5用二進制表示出來看看:
1 0 0
1 0 1
因為兩個數字不相同,所以它們的二進制碼肯定有一位是不同的。我們只需找出這一位,然后根據這一位上是0是1,將數組所有元素分到兩個新的數組中,這時4和5肯定已經被分到了不同的數組中,而其余兩個相同的數字肯定在一個數組中,這時就能分別對兩個數組進行異或運算了。
關於這一位,可以找右數第一個不同位,把兩數異或找出右邊第一個1即可,而兩數的異或其實就是原數組所有元素的異或。
var singleNumber = function(nums) {
var tmp = nums.reduce(function(pre, item) {
return pre ^ item;
});
var one = tmp & (-tmp); // 取右數第一位1
var a = [], b = [];
nums.forEach(function(item) {
item & one ? a.push(item) : b.push(item);
});
return [
a.reduce(function(pre, item) {
return pre ^ item;
}),
b.reduce(function(pre, item) {
return pre ^ item;
})
];
};
