位运算刷题技巧

刷题时如果出现O(1)的空间复杂度要求,或者是不能用加减乘数的符号来实现加减乘除,可以考虑位运算。以下的相关题目均为Leetcode上的题目。
几个知识点:
1、位运算是基于补码进行的。
2、最小的负数补码为0b1000....000,其不存在原码和反码。例如一个8位的整数能表示的范围是[-128, 127],其中-128的补码为0b1000 0000
3、如果a^b=c,那么有c^b=ac^a=b
4、a & -a 可以保留位中最右边 1 ,且将其余的 1 设位 0 的。例如6的二进制为0110,-6的二进制为1110,-6的补码为1010,因此6 ^ -6 = 0010(二进制)
5、a & (a-1)可以判断a是否是2的幂,即a是否是00010000这种形式,这是因为若a是该形式,则a-1为00001111,进行与运算会得到0。
6、a & 1可以判断a的奇偶,与a = a>>1结合可以逐位提取每个位的数字。
7、0x5 = 0101 可以校验二进制的奇数位,0xA = 1010可以校验偶数位。
8、a & 0b11111111可以提取a的前8位,1的个数可以调节。
9、a ^ 0b11111111可以把a的前8位取反,1的个数可以调节。
10、可以寻找数目为2^N的全排列。相关题目:784
11、前缀异或。如果存在一个数组arr和数组preXor,并且preXor[i]表示arr中前i个数字的异或,那么第i+1到第j个的异或可以表示为preXor[i]^preXor[j+1]。原因是上面的第3点。相关题目:1310。
12、若想知道arr数组中以索引i为结尾的位运算结果,即从0到i、1到i、2到i、…、i-1到i和i本身,除了前缀异或以外,还可以初始化一个状态数组statestate[i]表示i到当前索引的位运算结果。具体实现方法是:遍历到arr[i]时把该输放入state[i]中,在遍历到arr[i+1]时就可以对state[i]进行更新。这应该是滑动窗口问题系列通用的法则。相关题目:898

Python中使用位运算可能会遇到的问题

在 Python 中,整数类型为Unifying Long Integers, 即无限长整数类型。因此相较于其他语言的代码,有些涉及到负数的和期望溢出的Python代码需要进行额外的处理。
以16位数字为例,首先把有符号数[-128,127]转换为无符号数[0, 255],方法是将原来的数与0b1111 1111进行与运算,也就是提取前8位的数值。
例如:

  • 3 = 0b0000 0011(这是我们想要获得的)=0b0???000 0011(这是在python中的样子,问号表示若干个0),与0xFF与运算之后为0x3,也就是把最低的8位提取出来了。
  • -2 = 0b1000 0010(这是我们想要获得的),其补码为0b1111 1110(这是我们想要获得的)=0b1???111 1110(这是在python中的样子,问号表示若干个1),与0b1111 1111与运算之后为0b0???1111 1110(问号表示若干个0),这样它的值实际上是254(可以看成256-2)

这是因为,对于正数而言,我们不希望改变它。而对于负数而言,我们希望把[-128,-1]映射到[128,255](这里实际上在做的就是用负数的补码所代表的正数表示该负数)。

再啰嗦一句,这其实是补码在计算机加减中的本质。如果我们让3和-2相加,那么从十进制的角度来看,3+254=257,而由于整数范围最多到255,因此要对256取模,最终的结果为1。而从二进制的角度来看,0000 0011 + 1111 1110 = (1)0000 0001,括号中的1表示进位,但是由于整数是8位的,所以最高位溢出,最终的结果为1。

最后得到结果后,要把无符号整数映射回有符号整数,即把[128,255]的部分映射回[-128,-1]。只需要与0b1111 1111异或再取反即可。

  • 例如,将无符号数的165转换为有符号数,预期结果为-91(256-165=91),其二进制为0b10100101,与0b1111 1111异或,得到0b0101 1010,再按位取反,得到0b1010 0101,并且这是补码,转回成原码之后值为-91,与我们的预期一致。

再啰嗦一句,为什么要异或再取反?因为原先的二进制0b0000....00 1010 0101(前面的0表示python中的实际情况)是原码,我们希望计算机能把前8位当做反码,并且符号位要为1,因此先异或、这样的话对于前8位而言,若某位原先是1,异或后是0,取反后变回了1;某位原先是0,异或后是1,取反后是0。而对于其他位而言,只进行了取反(所以这里本质上在做的就是把除了前8位以外的位都进行了一次取反,是为了改变符号位),因此实际得到的补码为0b1111....11 1010 0101,把它变成原码后为0b1000....00 0101 1011,为-91。
相关题目:Leetcode 371

如何获取某一位上的值

使用掩码,例如为a=13,二进制为1101,定义变量mask = 1 << 2,将amask进行与运算,可以判断a的第2位(从右往左数,以第0位开始计算)是0还是1。

发布了19 篇原创文章 · 获赞 29 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_41987033/article/details/104576599