Hi 你們好,我是張小豬。歡迎來到『寶寶也能看懂』系列之 leetcode 周賽題解。git
這裏是第 171 期的第 2 題,也是題目列表中的第 1318 題 -- 『或運算的最小翻轉次數』github
給你三個正整數 a
、b
和 c
。shell
你能夠對 a
和 b
的二進制表示進行位翻轉操做,返回可以使按位或運算 a | b == c
成立的最小翻轉次數。segmentfault
「位翻轉操做」是指將一個數的二進制表示任何單個位上的 1 變成 0 或者 0 變成 1。spa
示例 1:code
輸入:a = 2, b = 6, c = 5 輸出:3 解釋:翻轉後 a = 1 , b = 4 , c = 5 使得 a OR b == c
示例 2:blog
輸入:a = 4, b = 2, c = 7 輸出:1
示例 3:ip
輸入:a = 1, b = 2, c = 3 輸出:0
提示:leetcode
1 <= a <= 10^9
1 <= b <= 10^9
1 <= c <= 10^9
MEDIUMget
看完題目後,第一預感,要面對位操做啦。想到我以前的代碼裏其實已經用到了一些位運算,而且有小夥伴問到相關的問題。因此咱們這裏先簡單說一些下面會用到的位操做,爲後續代碼中的使用做準備。
&
即與操做。它的處理邏輯相似於邏輯運算符 &&
,即 0 & 0 === 0
、0 & 1 === 0
、1 & 0 === 0
、1 & 1 === 1
。|
即或操做。它的處理邏輯相似於邏輯運算符 ||
,即 0 | 0 === 0
、0 | 1 === 1
、1 | 0 === 1
、1 | 1 === 1
。>>>
即無符號右移,就是把該數的二進制表示的值向右移動一位,超出的那一位會被丟棄。例如 5 的二進制是 101,而 2 的二進制是 10,因此 5 >>> 1 === 2
。<<
即左移,就是把該數的二進制表示的值向左移動一位。例如 5 的二進制是 101,而 10 的二進制是 1010,因此 5 << 1 === 10
。更多的關於位操做、二進制、數在計算機中的存儲方式等等,這裏不作展開。將來的新坑裏會詳細提到。
那麼如今咱們看回這道題吧。題目的要求是,給定了三個數 a
、b
和 c
,指望是執行 a | b
操做後的結果即爲 c
。若是達不到的話,能夠對 a
和 b
這兩個數的二進制值進行翻轉修改,即 0
變 1
、1
變 0
。返回須要最少須要進行翻轉修改的次數。
首先要注意的是,這裏的值都是指的二進制的值。而後結合咱們上面說到過的 |
或操做的運算邏輯,咱們能夠整理一個表格,便於觀察後續的處理方式。
a | b | a or b | c | flip |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 1 | 1 |
0 | 1 | 1 | 0 | 1 |
0 | 1 | 1 | 1 | 0 |
1 | 0 | 1 | 0 | 1 |
1 | 0 | 1 | 1 | 0 |
1 | 1 | 1 | 0 | 2 |
1 | 1 | 1 | 1 | 0 |
經過這個表格,咱們能夠輕易的看出,只有 4 種狀況須要進行翻轉操做。那麼爲了獲得最終的結果,咱們只須要遍歷二進制值的每一位,找出這 4 種狀況進行計數便可。這裏提供兩種遍歷的思路。
若是咱們想知道一個二進制值的第一位是 0 仍是 1,咱們應該怎麼作呢?回看上面咱們提到的位運算,會發現如何和 1 進行 &
與運算,那麼當目標值是 0 的狀況,會獲得 0;當目標值是 1 的狀況,會獲得 1。藉此咱們就能夠判斷第一位到底是 0 仍是 1了。那麼第二位呢,咱們直接經過無符號右移這個操做把它變成第一位便可。
上述方案的具體流程以下:
0
。若是 a
和 b
的第一位的或操做值不等於 c
的第一位:
基於以上流程,能夠實現相似下面的代碼:
const minFlips = (a, b, c) => { let ret = 0; while (a > 0 || b > 0 || c > 0) { if (((a & 1) | (b & 1)) !== (c & 1)) { ret += (a & 1) === 1 && (b & 1) === 1 ? 2 : 1; } a >>>= 1; b >>>= 1; c >>>= 1; } return ret; };
掩碼這個概念可能不是那麼常見,不過相信小夥伴們在設置 IP 的時候必定見過子網掩碼這個字段。其實咱們這裏說的掩碼就是一個用於進行預算的基準值,基於它和對應的運算能夠把沒必要要的信息剔除掉,只保留咱們須要的值。例如咱們這裏的需求其實就是想獲取到這三個數的二進制的每一位值。
因爲題目的條件種限制了三個數的範圍爲 [1, 10^9]
,因此咱們取個整數 32 位正好能夠覆蓋這個範圍。具體流程以下:
1
,計數爲 0
。對於 a
、b
和 c
,用掩碼取出對應的數值,並進行運算和比較:
基於以上流程,能夠實現相似下面的代碼:
const minFlips = (a, b, c) => { let ret = 0; let mask = 1; for (let i = 1; i < 32; ++i) { if (((a & mask) | (b & mask)) !== (c & mask)) { ret += (a & mask) === mask && (b & mask) === mask ? 2 : 1; } mask <<= 1; } return ret; };
這道題主要就是一些對於位運算的應用。自己題目內容並不難,不過須要一些二進制和位運算相關的知識。這部分的內容會比較偏向理論一些,因此這裏暫時先不展開了。不過在這道題目的處理過程當中,有個蠻有用的小方法,即咱們羅列一些數據和結果,特別是當可能性很少的時候能夠羅列全部可能性,每每會發現頗有效果的方案。