【位運算經典應用】 尋找那個惟一的數

Single Number


這一系列有三道題,第一題也是最簡單最經典的。數組

有一個數組,裏面的元素每一個都出現了兩次,除了一個特殊的,求這個特殊元素。接觸過這類題目的coder很快可以脫口而出:直接異或就ok了!的確如此:code

var singleNumber = function(nums) {
  return nums.reduce(function(pre, item) {
    return pre ^ item;
  });
};

可是爲什麼這樣能獲得答案?咱們假設有個數組[1, 2, 3, 1, 2],很顯然咱們要找出3這個元素。咱們首先將數組元素所有用二進制表示:leetcode

0 1
1 0
1 1
0 1
1 0

咱們從右往左,按位分析。若是數組中全部元素所有都是出現兩次,那麼每一位上的1的數量之和確定是2的倍數。咱們看從右往左的第一位,1出現了三次,這多出的一次就是3的起做用,從右往左數第二位,1一樣出現了三次,一樣是由於3的緣由。因此咱們能夠獲得該元素的二進制表示爲11,也就是3。get

繼續思考,咱們彷佛須要這麼一種「加法」運算,使得每位上的 1 bit 數量可以獲得累積,可是累積到2了就能清零。幸運的是,^運算符就是咱們要找的。it

因而代碼也就很好理解了。io

Single Number II


這是上題的增強版,數組中每一個元素都出現三次,除了一個特殊的,找出這個特殊元素。function

咱們以數組[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、一、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;
    })
  ];
};
相關文章
相關標籤/搜索