简介
计算机底层用的是机器码,即0和1交织而成。位运算在某些情况下,有着神奇的功效,能够提升效率,节省空间。此文对位运算的经典操作进行一些总结。
- 例如:
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例 1:输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
示例 2:输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]
可以采用异或操作得出两个不同数字的异或值(其他所有成对的数字异或后等于0),最终的结果每一位上若为1则表示两数在此位上不同,反之为0则相同。随便取为1的一位(由于俩数不同,必存在为1的位),将整个数组分为两组(以此位为0和一分别分两组),这样两个不同的数必然会分到两个不同的组中。最后两个不同的组中组内依次异或,可以计算出不同的那一位,两组得到最终两个不同的数。
class Solution:
def singleNumbers(self, nums: List[int]) -> List[int]:
x = 0
for i in range(len(nums)):
x ^= nums[i] # 求不同俩数异或结果
t = x - (x&(x-1)) # 取最低位1,树状数组的典型操作,位运算优先级较低加括号
ans = [0,0]
for x in nums: # 分组并异或
if t&x:
ans[0] ^= x
else:
ans[1] ^= x
return ans
详解
- 使用位运算求奇数偶数:
x&1==1 x&1==0
最低位为一为奇数,为零为偶数。用此方法的效率要高于一般的求余函数等操作。
- 使用位运算来交换变量:
a ^= b;
b ^= a;
a ^= b;
第一步:a ^= b ---> a = (a^b);
第二步:b ^= a ---> b = b^(a^b) ---> b = (b^b)^a = a
第三步:a ^= b ---> a = (a^b)^a = (a^a)^b = b
比使用临时变量更高效率。
- 交换符号将正数变成负数,负数变成正数:
~x+1
整数取反加1,正好变成其对应的负数(补码表示);负数取反加一,则变为其原码,即正数.
- 消去最低位1:
x&(x-1)
如果是x-(x&(x-1)
则为最取最低位1,这是树状数组中典型的lowbit操作的根本依据。
- 使用二进制进行子集枚举:
arr = [3,5,7]
编号 | 进制 | 子集 |
---|---|---|
0 | 000 | [] |
1 | 001 | [3] |
2 | 010 | [5] |
3 | 011 | [3,5] |
4 | 100 | [7] |
5 | 101 | [3,7] |
6 | 110 | [5,7] |
7 | 111 | [3,5,7] |
每一位代表当前数的取或者不取。
- 找出现一次的数:
在一个数组中,只有一个数出现一次,剩下都出现两次,找出出现一次的数。此时将每个数依次异或一遍,出现次数为两次的(偶数次均可),会相互抵消为0,剩下的就是只出现一次的数。
Bye
合理运用位运算可以有出其不意的效果,如开头提到的例子,如果用哈希表的话需要花费很多额外的空间,而使用位运算则只需常数复杂度就可以解决这个问题。