[寶寶也能看懂的 leetcode 活動][30-Day LeetCoding Challenge] 第一天

Hi 你們好,我是張小豬。歡迎來到『寶寶也能看懂』系列特別篇 - 30-Day LeetCoding Challengegit

這是一個 leetcode 官方的小活動,地址在這裏(若是被重定向回中文站了,去掉域名裏的 -cn 便可)。活動內容很簡單,從 4 月 1 號開始,天天會選出一道題(國內時間中午 12 點更新),在 24 小時內完成便可得到一點小獎勵。雖然獎勵彷佛也沒什麼用,不過做爲一個官方的打卡活動,小豬仍是來打一下卡吧,正好做爲天天下班回家後的娛樂。github

這裏是 4 月 1 號的題,也是題目列表中的第 136 題 -- 『只出現一次的數字』算法

題目描述

給定一個非空整數數組,除了某個元素只出現一次之外,其他每一個元素均出現兩次。找出那個只出現了一次的元素。shell

說明:segmentfault

你的算法應該具備線性時間複雜度。 你能夠不使用額外空間來實現嗎?數組

示例 1:數據結構

輸入: [2,2,1]
輸出: 1

示例 2:spa

輸入: [4,1,2,1,2]
輸出: 4

官方難度

EASYcode

解決思路

這道題彷佛沒什麼好講的,大概是做爲活動的開篇,預熱一下,吸引圍觀,激勵用戶參與。blog

下面給出幾個方案,供小夥伴們參考。

方案 1

其實這是個湊數的方案(哈哈哈,沒想到吧)。小豬相信不會有小夥伴們這麼去寫的,畢竟直接就到 O(n^2) 了,實在是太浪費了。權且做爲最最基礎的實現吧。

const singleNumber = nums => {
  const arr = [];
  for (const n of nums) {
    const idx = arr.indexOf(n);
    idx === -1 ? arr.push(n) : arr.splice(idx, 1);
  }
  return arr.pop();
};

方案 2

Set 代替了方案 1 中的數組,使得查詢和刪除都快了不少。不過代碼仍是太多了,而且也得基於額外的數據結構和空間,並不推薦。

const singleNumber = nums => {
  const set = new Set();
  for (const n of nums) {
    set.has(n) ? set.delete(n) : set.add(n);
  }
  for (const x of set.keys()) return x;
};

方案 3

用位操做代替了前兩種方案裏對於額外數據結構和空間的依賴。小豬以爲這個方案算是這道題的標準實現吧,纔不會告訴你前兩種方案是爲了寫文章才湊出來的呢 hia hia hia O(∩_∩)O

稍微解釋一下異或這個位操做吧,它的行爲邏輯能夠理解爲兩個值不一樣則爲 true,相同則爲 false。以下表:

0 ^ 0 === 0
0 ^ 1 === 1
1 ^ 0 === 1
1 ^ 1 === 0

那麼基於這個運算邏輯,咱們能夠發現,對於兩個相同的數值進行異或運算,那麼結果必定是 0。再看看咱們題目中的數據,正好是成對的數字加上一個單獨的數字。這時候再加上異或這個運算能夠知足交換律和結合律,因而咱們那些成對的數字運算完後正好爲 0,而 0 與一個數字進行異或預算即是這個數字自己。

基於這個思路,咱們能夠獲得以下的代碼:

const singleNumber = nums => {
  let ret = 0;
  for (const n of nums) ret ^= n;
  return ret;
};

固然,都寫成這樣了,不如咱們就直接一行吧:

const singleNumber = nums => nums.reduce((prev, cur) => prev ^ cur, 0);

拓展

關於異或操做的一些實際應用場景,這裏再補充幾個栗子吧。

交換兩個數

let a = 10;
let b = 20;
a = a ^ b;
b = b ^ a;
a = b ^ a;
console.log(a, b) // 20, 10

稍微解釋一下過程吧:

  1. 第一次異或操做獲得了兩個數字不一樣的位爲 1,相同的位爲 0,並保存進了變量 a
  2. 第二次異或操做至關因而把 b 中兩個數不一樣的位取反,相同的位保持不變,因而 b 就變成了 a,而後保存進變量 b 中。
  3. 第三次異或操做繼續把如今 b 中兩個數不一樣的位取反,相同的位保持不變。注意,這時候的 b 中的值實際上是 a,因此運算後獲得的值是 b,而後保存進變量 a 裏。
  4. 最終 a 就進了 b,b 就進了 a

兩個數相加

let a = 10;
let b = 20;
while (b !== 0) {
  const a2 = a ^ b;
  const b2 = (a & b) << 1;
  a = a2;
  b = b2;
}
console.log(a) // 30

同理,不過這裏面還用到了與操做和左移操做。原理其實和加法是同樣的,就是按位相加,而後須要進位的地方就進位。不過在二進制中,可能的狀況會比較少。具體以下:

  1. 經過異或運算找出按位相加後不會產生進位的地方。
  2. 經過與運算找出按位相加後會產生進位的地方,並作進位。
  3. 把上述兩個值保存後繼續執行相同的計算,直到加數減小爲 0,那麼被加數就變成了最初的兩數之和。

總結

這是 『30-Day LeetCoding Challenge』 的第一題,沒有太多能夠說的,因而就稍微拓展了一點關於異或操做的內容。但願能夠幫到有須要的小夥伴。

若是以爲不錯的話,記得『三連』哦。小豬愛大家喲~ >.<

相關連接

qrcode_green.jpeg

相關文章
相關標籤/搜索