力扣: 只出现一次的数字

78 阅读1分钟

image.png

对于这道题,我的思路就是遍历然后搞个map统计出现的次数。但是如果用了map就是用了额外的空间了,抓耳挠腮想不出别的方法,就干脆看题解了。结果看了题解后,恍然大悟,可以用异或实现呀,我就没想到还可以用这个来做,笑。

异或有如下几个性质:

  1. 一个数和0异或都得到它自身
  2. 一个数和它自身异或得到0
  3. 异或满足交换和结合率:a^b^a = a^a^b = 0^b = b

所以对于这道题,如果别的数字都出现两次,只有一个数字出现一次,那根据第三个规律,全部的数字异或起来最后得到的数字不就是这个出现一次的数字吗?so easy

var singleNumber = function(nums) {
    let res = 0;
    for(let i = 0 ; i < nums.length; i++) {
        res ^= nums[i];
    }

    return res;
};

写到这了,那就顺便在复习一下原码,反码和补码吧。

先说结论:

对于正数而言,它的原码,反码,补码是相同的。

对于负数而言,它的反码是将原码除了符号位之外,其余位都取反。它的补码则是将反码加1.

举个例子,假设都用一个字节表示数字,那么就有以下表格

数字原码反码补码
10000 00010000 00010000 0001
20000 00100000 00100000 0010
-21000 00101111 11011111 1110
-31000 00111111 11001111 1101

咱们来计算2^-3. 结果是-1.

根据MDN,在进行位运算时,所有的按位操作符的操作数都会被转成补码(two's complement)形式的有符号32位整数。异或操作的话对于每一个比特位,当两个操作数相应的比特位有且只有一个1时,结果为1,否则为0。

image.png

所以2^-3可以得到11111111 11111111 11111111 11111111,但是注意,这里得到的结果其实是补码,我们要将他转为原码再转为10进制才行。因为首位是1,所以可知这是一个负数,所以要求负数的原码那就是补码-1然后除过符号位后按位取反。所以有11111111 11111111 11111111 11111111 -1 -> 11111111 11111111 11111111 11111110 在按位取反-> 100000000 00000000 00000000 00000001 所以结果是-1