对于这道题,我的思路就是遍历然后搞个map统计出现的次数。但是如果用了map就是用了额外的空间了,抓耳挠腮想不出别的方法,就干脆看题解了。结果看了题解后,恍然大悟,可以用异或实现呀,我就没想到还可以用这个来做,笑。
异或有如下几个性质:
- 一个数和0异或都得到它自身
- 一个数和它自身异或得到0
- 异或满足交换和结合率: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.
举个例子,假设都用一个字节表示数字,那么就有以下表格
| 数字 | 原码 | 反码 | 补码 |
|---|---|---|---|
| 1 | 0000 0001 | 0000 0001 | 0000 0001 |
| 2 | 0000 0010 | 0000 0010 | 0000 0010 |
| -2 | 1000 0010 | 1111 1101 | 1111 1110 |
| -3 | 1000 0011 | 1111 1100 | 1111 1101 |
咱们来计算2^-3. 结果是-1.
根据MDN,在进行位运算时,所有的按位操作符的操作数都会被转成补码(two's complement)形式的有符号32位整数。异或操作的话对于每一个比特位,当两个操作数相应的比特位有且只有一个1时,结果为1,否则为0。
所以2^-3可以得到11111111 11111111 11111111 11111111,但是注意,这里得到的结果其实是补码,我们要将他转为原码再转为10进制才行。因为首位是1,所以可知这是一个负数,所以要求负数的原码那就是补码-1然后除过符号位后按位取反。所以有11111111 11111111 11111111 11111111 -1 -> 11111111 11111111 11111111 11111110 在按位取反-> 100000000 00000000 00000000 00000001 所以结果是-1