今天在火車站候車,實在無聊,不如把以前領略過的算法復個盤。先亮出題目:面試
這一題自己並不難,難的是進一步的優化。直接AC過去簡單,但能不能經得住更深刻的提問,很是考驗面試者的數學功底和科班素養。簡而言之,看你到底懂不懂計算機的二進制操做。算法
/** * @param {number[]} nums * @return {number[]} */
var singleNumber = function(nums) {
let hash = {};
let res = [];
for(let i = 0; i < nums.length; i++) {
if(hash[nums[i]] !== undefined) {
hash[nums[i]]++;
}else {
hash[nums[i]] = 1;
}
}
Object.keys(hash).map(item => {
if(hash[item] === 1) res.push(item);
})
return res;
};
複製代碼
事實上這個算法的時間複雜度是O(n),已經很優秀了。空間複雜度也是O(n),有沒有優化的餘地呢?數組
有,並且可讓空間複雜度降到O(1),也就是常數級別。性能
首先簡單科普一下異或操做,相同數異或爲0,相異則結果爲1。優化
如 1 ^ 1 = 0, 0 ^ 1 = 1ui
那麼由此能夠推出一些結論:spa
所以,咱們第一步是這樣:code
let sign = 0;
for(let i = 0; i < nums.length; i ++){
sign ^= nums[i];
}
複製代碼
如今sign的結果是什麼呢?若是隻有一個不重複的數,那還好說,重複兩次的數都抵消了,異或完的結果就是這個數。可是如今是兩個數,應該如何來處理呢?cdn
首先,有一點是很是肯定的,那就是兩個不重複數的二進制位必定有一位不一樣,也就是說異或完成的結果必定有一位是1。咱們找出這一位,而後本身另外定義一個只有這一位是一、其它位都是0的數,用這個數和數組中每一個數相與,若是相與結果爲0,放到第一組,不然放到另一組。每組的數字都所有進行異或,最後每組重複的數已經消掉,只剩下一個數,即不重複的數,這就把兩個不重複的數分開了。blog
let n = 1;
let res = [0, 0];
while((n & sign) === 0){
n <<= 1;//左移一位
}
for(let i = 0; i < nums.length; i++) {
if((nums[i] & n) === 0) {
res[0] ^= nums[i];
}else {
res[1] ^= nums[i];
}
}
return res;
複製代碼
能夠看到,這一次的空間複雜度完全地降到了O(1)級別,已經很是優秀了。再次提交,性能也是有了質的飛躍。
不過,你們回過頭反思咱們尋找第一次遍歷後尋找異或結果中1的位置這個過程,可能會有更直接、更本質的操做,那就是:可讓它和它的相反數進行與操做。
由於計算機裏面對於負數的存儲是採用符號位置1,後面的位取反加一,這樣會有一個規律:
一個正整數和它的相反數進行相與操做,結果中必定只含一個一,其餘位位0,並且這個1的位置正好是這個正整數最後一個1的位置。
即上面求n的過程能夠直接寫爲:
let n = sign & (-sign);
複製代碼
再次提交,依然AC。