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


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;
    })
  ];
};


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM