本題其實算是比較簡單,在 leetcode 上也只是 medium 級別,ac 率也很高,最好先本身嘗試,本文只是單純的記錄一下本身總體的思路;算法
在閱讀本文章以前,最好先解鎖本題的簡單模式 136.Single Number,這對理解本題有較大的幫助;數組
還有不少細節做者都沒有寫進去,是爲了留給讀者一點思考的空間,實際上是由於懶;spa
因爲做者我的水平等緣由,出現錯誤在所不免,還望各位看官海涵。3d
注意
Note
中的第一個條件:The order of the result is not important.,這個條件很是重要,這關係到算法的正確性。code
而後給出整個算法的具體思路,假設數組中兩個不一樣的數字爲 A 和 B;blog
經過遍歷整個數組並求整個數組全部數字之間的 XOR,根據 XOR 的特性能夠獲得最終的結果爲
AXORB = A XOR B
;圖片經過某種特定的方式,咱們能夠經過 AXORB 獲得在數字 A 和數字 B 的二進制下某一位不相同的位;由於A 和 B 是不相同的,因此他們的二進制數字有且至少有一位是不相同的。咱們將這一位設置爲 1,並將全部的其餘位設置爲 0,咱們假設咱們獲得的這個數字爲 bitFlag;ip
那麼如今,咱們很容易知道,數字 A 和 數字 B 中
必然有一個數字與上 bitFlag 爲 0
;由於bitFlag 標誌了數字 A 和數字 B 中的某一位不一樣,那麼在數字 A 和 B 中的這一位必然是一個爲 0,另外一個爲 1;而咱們在 bitFlag 中將其餘位都設置爲 0,那麼該位爲 0 的數字與上 bitFlag 就等於 0,而該位爲 1 的數字與上 bitFlag 就等於 bitFlagleetcode如今問題就簡單了,咱們只須要在循環一次數組,將與上 bitFlag 爲 0 的數字進行 XOR 運算,與上 bitFlag 不爲 0 的數組進行獨立的 XOR 運算。那麼最後咱們獲得的這兩個數字就是 A 和 B。
先給出具體實現,引用自 proron's Java bit manipulation solution,我修改了部分代碼以便於理解:
public class Solution { public int[] singleNumber(int[] nums) { int AXORB = 0; for (int num : nums) { AXORB ^= num; } // pick one bit as flag int bitFlag = (AXORB & (~ (AXORB - 1))); int[] res = new int[2]; for (int num : nums) { if ((num & bitFlag) == 0) { res[0] ^= num; } else { res[1] ^= num; } } return res; } }
接下來,咱們一行行的解析代碼:
int AXORB = 0; for(int num: nums){ AXORB ^= num; }
這段代碼在 136.Single Number 已經解析過,很容易理解最後獲得的結果:假設數組中不一樣的數字爲 A 和 B,那麼 最後獲得的結果是 A XOR B。
隨後的這一行代碼是整個算法中的難點:
//pick one bit as flag int bitFlag = (AXORB & (~ (AXORB - 1)));
這一行代碼的做用是:找到數字 A 和數字 B 中不相同的一位,並將該位設置爲 1,其餘位設置爲 0;
根據 XOR 的定義,咱們知道,在 AXORB 中,爲 1 的位即 A 和 B 不相同的位,AXORB 中爲 0 的位即 A 和 B 中相同的位
因此,要找到 A 和 B 中不相同的位,只須要找到在 AXORB 中從右往左第一個爲 1 的位,保留該位並將其餘位置爲 0 便可。
//其實這一行與下面的代碼等價,可是論逼格就差遠了(手動斜眼 public static int f(int num){ int times = 0; while(num > 0){ if(num % 2 == 1){ break; } times++; num = num >> 1; } return 1 << times; } //下面這個返回 true System.out.println(Stream.iterate(1, num -> num + 1).limit(Integer.MAX_VALUE).allMatch(num -> f(num)==(num & (~(num -1)))));
咱們能夠把這一行代碼解析爲三步:
int tmp0 = AXORB - 1;
假設 AXORB 從右往左出現的第一位非 0 數字出如今第k位
,那麼數字 AXORB 能夠表示爲,可能等於 0:
若是 a0 = 1;那麼問題很是簡單, AXORB - 1 能夠表示爲:
int tmp1 = ~tmp0;
int bitFlag = AXORB & tmp1;
由前面假設咱們知道 a0=1,因此很明顯, bitFlag = 1;
若是 a0 != 1,咱們一樣很容易得出這一行代碼的做用就是將數字 AXORB 的從右到左第一個出現 1 的位置爲 1,其餘位所有置爲 0。
其實一點都不容易,只不過用 laTex 寫數學公式好蛋疼啊,因此偷下懶
到這裏,整個算法基本上就沒什麼難點了,你們自行理解吧。
我了個大槽,我原本只是想試試新學的 laTex,結果他喵的就寫了這麼多。大寫加粗的坑