本题其实算是比较简单,在 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,结果他喵的就写了这么多。大写加粗的坑