「求只出現一次的數字」系列算法問題

目前遇到的「求只出現一次的數字」系列的算法題目主要有如下三個:javascript

  • 題目一:數組中只有一個元素只出現一次,其他的元素都出現兩次,求這個元素;
  • 題目二:數組中只有兩個元素只出現一次,其他的元素都出現兩次,求這個元素;
  • 題目二:數組中只有一個元素只出現一次,其他的元素都出現三次,求這個元素;

這類算法題目的解決方案主要有如下幾種:java

方案一:統計每一個數字的出現次數

創建一個 Map(key 爲數組中的元素,value 爲該元素出現的次數),遍歷數組中的全部元素,最後再遍歷 Map 找到只出現一次的元素。算法

function singleNumber(nums = []) {
    let map = {};
    nums.forEach(num => map[num] ? map[num]++ : (map[num] = 1));
    return Object.keys(map).filter(num => map[num] === 1).map(num => parseInt(num));
}
複製代碼

該方法優勢是比較簡單,能夠解決上面三個題目,可是空間複雜度比較高,若是數組元素比較多時,map 佔用空間太大。數組

方案二:統計每一個二進制位出現次數

由於題目中的數字是 Int 類型,能夠創建一個大小爲 32 的數組,來統計每一位上 1 出現的個數,若是某一位上爲 1 的話,那麼若是該整數出現了三次或者兩次(看題目中其他元素出現的次數),對 3 或者 2(具體看題目中) 取餘爲 0,這樣把每一個數的對應位都加起來對 3 或者 2 取餘,最終剩下來的那個數就是單獨的數字:大數據

function singleNumber(nums = []) {
    let res = 0;
    for (let i = 0; i < 32; i++) {
        let sum = 0;
        for (let j = 0; j < nums.length; j++) {
            sum += (nums[j] >> i) & 1;
        }
        res |= (sum % 2) << i;
    }
    return res;
}
複製代碼

這個對於大數據量的狀況下,相對於方案一,空間複雜度要小不少,可是這個方案只能解決題目一和題目三的問題,對於題目二中,有兩個元素只出現一次卻無能爲力。ui

方案三:位運算的靈活應用

《題目一》位運算解決方案:

想到位運算中的異或操做可使兩個相同的數字進行異或操做結果必爲 0,那麼咱們能夠遍歷整個數組,對全部元素進行異或操做,全部出現兩次的元素異或結果都變成 0 了,那麼剩下的確定就是那個只出現一次的數字了,是否是很神奇:spa

function singleNumber(nums = []) {
    let res = 0;
    nums.forEach(num => res = res ^ num);
    return res;
}
複製代碼

這個解法代碼量少,時間複雜度和空間複雜度都最低。code

《題目二》位運算解決方案:

若是隻有一個數字出現一次,咱們能夠經過異或的方式直接獲得該數字,若是兩個數字只出現一次的狀況下,獲得的異或的結果必然是這兩個數字的異或的結果,那麼咱們如何拆分呢?咱們能夠想到,若是這個結果某個位等於 1(隨便找一個爲 1 的位便可),那麼必然是這兩個數字裏對應的二進制位一個是 0, 一個是 1,那麼咱們就能夠根據該位把列表裏全部的數字分爲兩類,一類是該位是 0 的列表,一類是該位是 1 的列表,那麼《題目二》就被拆解成《題目一》的兩個子問題,一樣能夠獲取到這兩個只出現一次的數字:three

function singleNumber(nums = []) {
    let res = 0;
    nums.forEach(num => res = res ^ num);
    let i = 0;
    while(!((res >> i) & 1)){
        i++;
    }
    let res1 = 0, res2 = 0;
    nums.forEach(num => {
        if((num >> i) & 1){
            res1 = res1 ^ num;
        }else{
            res2 = res2 ^ num;
        }
    });
    return [res1, res2];
}

複製代碼
《題目三》位運算解決方案:

對於《題目三》,若是咱們直接使用方案一和方案的方式去解決問題,好像並無論用,由於其他元素出現的次數是三次,異或並不能使其他元素結果變爲 0,若是異或操做的三進制,那麼或許能夠,惋惜異或目前只能操做二進制,那麼有沒有一種方案能夠模擬三進制操做呢,答案是確定的。ip

咱們能夠用 3 個整數來表示各位出現 1 的次數狀況:

  • ones 表示每一個位只出現了 1 次 1 的狀況
  • twos 表示每一個位只出現了 2 次 1 的狀況
  • threes 表示每一個位只出現 3 次 1 的狀況
function singleNumber(nums) {
    let one = 0, two = 0, three = 0;
    for (let i = 0; i < nums.length; i++) {
        //若是最近連續兩次爲 1,或者原先 two 就爲 1,那麼 two 就爲 1,two 的計算必須放在 one 前面,這裏的 & 是與上一次的 one 
        two |= one & nums[i];  
        //每次經過異或更新只出現的一次的結果,和《題目一》《題目二》的相似
        one ^= nums[i];
        //若是一個位既出現了一次,也出現了兩次,表示這個數字出現了3次
        three = one & two;
        //清零 one,two 中出現三次的位
        one &= ~three;
        two &= ~three;
    }
    return one;
}
複製代碼
相關文章
相關標籤/搜索