在阅读之前先补充一下计算机的二进制运算
逻辑运算:与,或,非,异或运算 XOR
-
与
运算符号为&,运算规律是:真真为真,真假为假,假假为假
-
或
运算符号为|,运算规律是:真真为真,真假为真,假假为假
-
非
运算符为~,取反的逻辑,运算规律:二进制位若为1,取反后为0。若为0,取反后为1
-
异或运算XOR
- 一个值与自身的运算,总是为 false。
x ^ x = 0
- 一个值与 0 的运算,总是等于其本身。
x ^ 0 = x
正数、负数的表示
对于负数的表示,比较特殊,其有一条运算规律:对非负数的二进制进行取反、然后+1,便可得其负数的二进制表示
加减法运算过程
- 加法
类似十进制的进位方式,二进制逢二进一
-
减法
可以看作加法,5-3可以看作5+(-3)
练练手
136. 只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
class Solution:
def singleNumber(self, nums: List[int]) -> int:
res = 0
for i in nums:
res = res ^ i
return res
260. 只出现一次的数字 III
给定一个整数数组 nums
,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。
方法一
与上面那道题不同的是一组里面有两个不同的数,那么我们除了用哈希还可以用位运算解决这个问题!
首先,有一点是非常确定的,那就是两个不重复数的二进制位一定有一位不同,也就是说异或完成的结果一定有一位是1。我们找出这一位,然后自己另外定义一个只有这一位是1、其它位都是0的数,用这个数和数组中每个数相与,如果相与结果为0,放到第一组,否则放到另外一组。每组的数字都全部进行异或,最后每组重复的数已经消掉,只剩下一个数,即不重复的数,这就把两个不重复的数分开了。
不过,大家回过头反思我们寻找第一次遍历后寻找异或结果中1的位置这个过程,可能会有更直接、更本质的操作,那就是:可以让它和它的相反数进行与操作。
一个正整数和它的相反数进行相与操作,结果中一定只含一个一,其他位位0,而且这个1的位置正好是这个正整数最后一个1的位置。
m = ero & -ero
然后每个数进行与运算,这样可以进行分成两组,那两组分别进行异或运算,自然可以找出这两个数啦!
class Solution:
def singleNumber(self, nums: List[int]) -> List[int]:
ero = 0
for num in nums:
ero ^= num
m = ero & -ero
res = [0] *2
for num in nums:
if m & num == 0:
res[1] ^= num
else:
res[0] ^= num
return res
方法二
- 因为a^a = 0,先遍历数组对所有数异或,得到两个只出现一次的数的异或结果eor
- 通过eor & - eor取得eor的最右边的1,令其为rightOne。因为两个数对应位不同异或后该位才会为1,所以rightOne对应位在两个数中一定是一个为0,一个为1,这是在为后面分组做准备
- 再遍历数组,每个数和rightOne与,将所有数分成与运算后为0和为1两组,要找的两个数分别在两组
- 把其中一组的所有数异或,得到其中一个要找的数res,另一个数即为res ^ eor
class Solution:
def singleNumber(self, nums: List[int]) -> List[int]:
eor = 0
for num in nums:
eor ^= num
rightOne = eor & - eor
res = 0
for num in nums:
if num & rightOne != 0:
res ^= num
return [res, res ^ eor]