leetcode 算法解析(一):260. Single Number III(C++版本和自己的注解)

这个题来自《剑指offer》但是书上上感觉讲解不太详细,还是看博客吧(我把下面博客改写成了C++版本运行通过)

注意这个题的相关代码中,输入的数组只能有两个数出现一次,如果有第三个数出现一次,那么这个代码就会失效。

总结下算法思路:

假设原始数组中只出现一次的元素是A和B,原始数组为{A,E,C,D,C,D,E,B}

主要是利用异或的交换律。

先把所有数字按次序进行异或运算,得到的结果必然是A⊕B,因为其他元素都是出现两次的,根据异或运算的交换律有

A⊕E⊕C⊕D⊕C⊕D⊕E⊕B=A⊕B⊕C⊕C⊕D⊕D⊕E⊕E=A⊕B

这个A⊕B中可能有多个比特位是1,我们取最右侧的一个bit(其实其他bit也行)

先把原始数组拆成两份,怎么拆呢?利用上面的那个A⊕B的最右侧的值为1的bit位(其他位置为0),

和这个bit位&结果为非0的归为1组并进行异或运算(异或运算的初始值为0),

和这个bit位&结果为0的归为另外1组并进行异或运算(异或运算的初始值为0)

在归为一组的同时进行异或运算,所以其实这里再次使用了异或运算的交换律,

因为C,D,E都出现两次,无论归为哪一组,异或结果都是0,我们可以不关心。

好了,所以其实就是剩下的A和B与上面的bit进行运算的结果是最关键的。

因为A⊕B的最右侧的bit位表示了A和B在二进制的某位上的不一致,也就是在这个位上必定一个是1,一个是0,

所以当A和(A⊕B的最右侧的bit位)进行&运算时,要么相等,要么不相等。

相等时丢入其中一组去做异或运算,因为其他元素都是出现两次,所以最终异或结果肯定是这个只出现一次的数

不相等时丢入其中一组去做异或运算,因为其他元素都是出现两次,所以最终异或结果肯定是这个只出现一次的数

文章转载自:

https://segmentfault.com/a/1190000004886431

260.Single Number II 原题链接

  • 本题其实算是比较简单,在 leetcode 上也只是 medium 级别,ac 率也很高,最好先自己尝试,本文只是单纯的记录一下自己整体的思路;

  • 在阅读本文章之前,最好先解锁本题的简单模式 136.Single Number,这对理解本题有较大的帮助;

  • 还有很多细节作者都没有写进去,是为了留给读者一点思考的空间,其实是因为懒

  • 由于作者个人水平等原因,出现错误在所难免,还望各位看官海涵。

注意 Note 中的第一个条件:The order of the result is not important.,这个条件非常重要,这关系到算法的正确性。


然后给出整个算法的具体思路,假设数组中两个不同的数字为 A 和 B;

  1. 通过遍历整个数组并求整个数组所有数字之间的 XOR,根据 XOR 的特性可以得到最终的结果为 AXORB = A XOR B

  2. 通过某种特定的方式,我们可以通过 AXORB 得到在数字 A 和数字 B 的二进制下某一位不相同的位;因为A 和 B 是不相同的,所以他们的二进制数字有且至少有一位是不相同的。我们将这一位设置为 1,并将所有的其他位设置为 0,我们假设我们得到的这个数字为 bitFlag;

  3. 那么现在,我们很容易知道,数字 A 和 数字 B 中必然有一个数字与上 bitFlag 为 0

  4.  因为bitFlag 标志了数字 A 和数字 B 中的某一位不同,那么在数字 A 和 B 中的这一位必然是一个为 0,另一个为 1

  5. 而我们在 bitFlag 中将其他位都设置为 0,那么该位为 0 的且只出现一次的数字& bitFlag 就等于 0,而该位为 1 的且只出现一次的数字与上 bitFlag 就等于 bitFlag

  6. 现在问题就简单了,我们只需要在循环一次数组,将与上 bitFlag 为 0 的数字进行 XOR 运算,与上 bitFlag 不为 0 的数组进行独立的 XOR 运算。那么最后我们得到的这两个数字就是 A 和 B。

先给出具体实现,引用自 proron's Java bit manipulation solution,我修改了部分代码以便于理解:

#include  
using namespace std;
int* singleNumber(int* nums,int length)
{
    int AXORB = 0;
    for (int i = 0; i

接下来,我们一行行的解析代码:

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,结果他喵的就写了这么多。大写加粗的坑


你可能感兴趣的:(《剑指Offer》)