本题其实算是比较简单,在 leetcode 上也只是 medium 级别,ac 率也很高,最好先自己尝试,本文只是单纯的记录一下自己整体的思路;
在阅读本文章之前,最好先解锁本题的简单模式 136.Single Number,这对理解本题有较大的帮助;
还有很多细节作者都没有写进去,是为了留给读者一点思考的空间,
其实是因为懒;由于作者个人水平等原因,出现错误在所难免,还望各位看官海涵。
注意
Note
中的第一个条件:The order of the result is not important.,这个条件非常重要,这关系到算法的正确性。
然后给出整个算法的具体思路,假设数组中两个不同的数字为 A 和 B;
通过遍历整个数组并求整个数组所有数字之间的 XOR,根据 XOR 的特性可以得到最终的结果为
AXORB = A XOR B
;通过某种特定的方式,我们可以通过 AXORB 得到在数字 A 和数字 B 的二进制下某一位不相同的位;因为A 和 B 是不相同的,所以他们的二进制数字有且至少有一位是不相同的。我们将这一位设置为 1,并将所有的其他位设置为 0,我们假设我们得到的这个数字为 bitFlag;
那么现在,我们很容易知道,数字 A 和 数字 B 中
必然有一个数字与上 bitFlag 为 0
;因为bitFlag 标志了数字 A 和数字 B 中的某一位不同,那么在数字 A 和 B 中的这一位必然是一个为 0,另一个为 1;而我们在 bitFlag 中将其他位都设置为 0,那么该位为 0 的数字与上 bitFlag 就等于 0,而该位为 1 的数字与上 bitFlag 就等于 bitFlag现在问题就简单了,我们只需要在循环一次数组,将与上 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,结果他喵的就写了这么多。大写加粗的坑
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。