[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. 复杂度分析
- O(1)
3. 常见应用
- 各种位运算题目。
- 需要状态压缩的场景。
- 树状数组等需要快速移位的场景。
4. 常用优化
- 如果熟练可以避免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。
- 如果限制不超过limit,做法见灵神GitHub
print((1 << (l ^ r).bit_length()) - 1)
三、其他
四、更多例题
五、参考链接