[python刷题模板] 位运算技巧

  • 一、 算法&数据结构
  • 1. 描述
  • 2. 复杂度分析
  • 3. 常见应用
  • 4. 常用优化
  • 二、 模板代码
  • 1. 仅剩lowbit(只保留最低位的1,其余置0): lowbit = lambda x:x&-x
  • 2. 去掉lowbit保留其余位(去掉最低位的1):x &= (x-1)
  • 3. 把最低位的0置1其余保留(填上最低位的0):x |= x+1
  • 4. 判断一个数x是不是2的次幂(x的因子只含2或1) x>0 and x&(x-1) == 0
  • 5. 判断一个数x是否含有相邻的1,x>>1&x>0
  • 6. 判断一个数x是否含有相邻的0(不考虑前导0),x|=x>>1;return x&(x+1)>0
  • 7. 判断一个数x的二进制里是不是全是1(不考虑前导0),x&(x+1)==0
  • 8. 判断一个数a里的位1是不是都包含在b里(子集),a|b=b
  • 9. 从a里删除b里含有的1,类似集合的差集 return (((1 << a.bit_length()) - 1) ^ b) & a
  • 10. 获取a,b两个数的二进制最长公共前缀,循环去掉大的那个数的lowbit(同时这也是[low,high]区间内所有数的位与结果)
  • 11. 获取[low,high]区间内选两个数最大的位或。
  • 三、其他
  • 四、更多例题
  • 五、参考链接


一、 算法&数据结构

1. 描述

本文整理一些位运算的技巧。

2. 复杂度分析

  1. O(1)

3. 常见应用

  1. 各种位运算题目。
  2. 需要状态压缩的场景。
  3. 树状数组等需要快速移位的场景。

4. 常用优化

  1. 如果熟练可以避免function call。

二、 模板代码

1. 仅剩lowbit(只保留最低位的1,其余置0): lowbit = lambda x:x&-x

  • 考虑数 x = 100010
  • x取反 ~x = 011101
  • ~x+1即-x = 011110
  • 发现x&-x正好只剩下最低位的1。
  • 为什么要+1呢:因为我们取反后目的是把相反数最后的1都干掉,最低位的0变成1,+1即可。

  • 在树状数组中经常用到这个。
lowbit = lambda x:x&-x

2. 去掉lowbit保留其余位(去掉最低位的1):x &= (x-1)

  • 考虑数 x = 100010
  • 则x-1 : x = 100001
  • x &(x-1) = 100000
  • 这是因为-1操作可以正好把低位的连续0都变1,向lowbit借位,不影响更高位。

  • 另外,也可以直接减去lowbit,即x -= x&-x
x &= x-1
x -= x&-x

3. 把最低位的0置1其余保留(填上最低位的0):x |= x+1

  • 考虑x = 100101
  • x+1 = 100110
  • x|(x+1) = 100111
  • 因为+1操作可以越过最低位的连续1,把第一个0置1,而不影响更高位;我们取或操作,低位置0的位置没关系。
x |= x+1

4. 判断一个数x是不是2的次幂(x的因子只含2或1) x>0 and x&(x-1) == 0

  • 去掉lowbit就是0,那么说明这个数只含一个1。
is_pow2 = lambda x:x>0 and x&(x-1) == 0

5. 判断一个数x是否含有相邻的1,x>>1&x>0

  • 把x右移一位,和自己与操作,如果还有1则说明有相邻的1。
has_adjacent_ones = lambda x:(x>>1)&x>0

6. 判断一个数x是否含有相邻的0(不考虑前导0),x|=x>>1;return x&(x+1)>0

  • 把x右移一位,和自己与操作,如果还有1则说明有相邻的1。
def has_adjacent_zeros(x):
	x|=x>>1  # 如果没有相邻的0,x会变成全1的数
	return x&(x+1)>0

7. 判断一个数x的二进制里是不是全是1(不考虑前导0),x&(x+1)==0

  • x+=1;return x&(x-1)==0即可。
  • 全1的数加上1一定是个2的次幂。
only_has_ones = lambda x:x&(x+1)==0

8. 判断一个数a里的位1是不是都包含在b里(子集),a|b=b

  • 方法一,显然如果a不存在b原有的1以外的1的话,位或的结果依然是b。
  • 方法二,a&b==a,因为b如果完全涵盖a,那么与的结果a里的1一定都能保留。
a_in_b = lambda a,b:a|b == b
a_in_b2 = lambda a,b:a&b == a

9. 从a里删除b里含有的1,类似集合的差集 return (((1 << a.bit_length()) - 1) ^ b) & a

  • 要把a中的1干掉,则把b里对应位1变成0,然后位与起来即可;
  • 如何把b中对应位的1变成0,异或一个全1的数或者取反。
a = int('101011', 2)
b = int('001110', 2)
c = int('100000', 2)


# 移除a中所有b里的1(即移除b中同位置上的1)
def remove_1_in_b_from_a(a, b):
    return (((1 << a.bit_length()) - 1) ^ b) & a


print(bin(remove_1_in_b_from_a(a, b)))
print(bin(remove_1_in_b_from_a(b, a)))

10. 获取a,b两个数的二进制最长公共前缀,循环去掉大的那个数的lowbit(同时这也是[low,high]区间内所有数的位与结果)

  • 当low<high时去掉high的lowbit,最后留下的high就是最长公共前缀。
  • 同时这也是[low,high]区间内所有数的位与结果。201. 数字范围按位与
def get_common_prefix(a,b):
    if a>b:
        a,b=b,a
    while a<b:
        b&=b-1
    return b

11. 获取[low,high]区间内选两个数最大的位或。

  • cf276d D. Little Girl and Maximum XOR
  • 如果 L 和 R 的二进制长度不一样,例如 L=2,R=9,那么我们可以用 7^8 得到最大的异或和 15。
  • 推广,如果 L 和 R 的二进制长度一样,那么我们可以从高到低找到第一个二进制不同的位置,转换到长度不一样的情况。
  • 总之,答案为 (1 << bit_length(L ^ R)) - 1。

print((1 << (l ^ r).bit_length()) - 1)

三、其他


四、更多例题

五、参考链接