30 秒速览
- 核心操作:
&(与)、|(或)、^(异或)、~(非)、<<、>> - 常见技巧:
n & (n-1)消除最低 1、n & (-n)获取最低 1 - 异或性质:
a ^ a = 0、a ^ 0 = a、满足交换律结合律 - 典型应用:找唯一数、位掩码、状态压缩 DP
一、直觉建立
位运算是什么?
把数字看成二进制,直接操作每一位。
5 = 0101
3 = 0011
-------
5 & 3 = 0001 (1)
5 | 3 = 0111 (7)
5 ^ 3 = 0110 (6)
开关比喻
把每一位想象成一个开关:
& 1:检查开关状态| 1:打开开关& 0:关闭开关^ 1:切换开关
二、基本操作
位运算符
a & b # 与:两位都为 1 才为 1
a | b # 或:有一位为 1 就为 1
a ^ b # 异或:两位不同为 1
~a # 非:取反
a << n # 左移:相当于 × 2^n
a >> n # 右移:相当于 ÷ 2^n
常用技巧
# 判断奇偶
n & 1 # 1 是奇数,0 是偶数
# 交换两个数
a ^= b
b ^= a
a ^= b
# 消除最低位的 1
n & (n - 1)
# 获取最低位的 1
n & (-n)
# 判断是否是 2 的幂
n > 0 and (n & (n - 1)) == 0
# 取第 k 位
(n >> k) & 1
# 设置第 k 位为 1
n | (1 << k)
# 设置第 k 位为 0
n & ~(1 << k)
# 切换第 k 位
n ^ (1 << k)
三、典型例题
例题 1:LC 136. 只出现一次的数字
题目:数组中除了一个元素只出现一次,其他都出现两次。找出那个元素。
异或法
利用异或性质:
a ^ a = 0a ^ 0 = a- 异或满足交换律、结合律
def singleNumber(nums: list[int]) -> int:
result = 0
for num in nums:
result ^= num
return result
复杂度:时间 O(n),空间 O(1)
例题 2:LC 191. 位 1 的个数
题目:统计整数的二进制表示中有多少个 1。
方法一:n & (n-1)
每次消除最低位的 1。
def hammingWeight(n: int) -> int:
count = 0
while n:
n &= (n - 1) # 消除最低位的 1
count += 1
return count
方法二:逐位检查
def hammingWeight(n: int) -> int:
count = 0
for _ in range(32):
count += n & 1
n >>= 1
return count
复杂度:时间 O(1)(固定 32 位),空间 O(1)
例题 3:LC 338. 比特位计数
题目:对 0 到 n 的每个数,计算其二进制表示中 1 的个数。
DP 解法
dp[i] = i 的二进制中 1 的个数
def countBits(n: int) -> list[int]:
dp = [0] * (n + 1)
for i in range(1, n + 1):
# i 比 i >> 1 多了最低位
# 如果 i 是奇数,最低位是 1,否则是 0
dp[i] = dp[i >> 1] + (i & 1)
return dp
或者用 i & (i-1):
def countBits(n: int) -> list[int]:
dp = [0] * (n + 1)
for i in range(1, n + 1):
# i 比 i & (i-1) 多一个 1
dp[i] = dp[i & (i - 1)] + 1
return dp
复杂度:时间 O(n),空间 O(n)
例题 4:LC 461. 汉明距离
题目:两个整数之间的汉明距离是二进制表示中不同位的个数。
异或后数 1
def hammingDistance(x: int, y: int) -> int:
xor = x ^ y
count = 0
while xor:
xor &= (xor - 1)
count += 1
return count
复杂度:时间 O(1),空间 O(1)
例题 5:LC 268. 丢失的数字
题目:[0, n] 中缺失了一个数字,找出它。
异或法
def missingNumber(nums: list[int]) -> int:
result = 0
n = len(nums)
# 异或所有索引和数值
for i, num in enumerate(nums):
result ^= i ^ num
# 最后异或 n
return result ^ n
复杂度:时间 O(n),空间 O(1)
例题 6:LC 231. 2 的幂
题目:判断一个数是否是 2 的幂。
位运算
2 的幂只有一个 1。
def isPowerOfTwo(n: int) -> bool:
return n > 0 and (n & (n - 1)) == 0
例题 7:LC 190. 颠倒二进制位
题目:颠倒给定的 32 位无符号整数的二进制位。
逐位颠倒
def reverseBits(n: int) -> int:
result = 0
for _ in range(32):
result = (result << 1) | (n & 1)
n >>= 1
return result
例题 8:LC 421. 数组中两个数的最大异或值
题目:找数组中两个数的最大异或值。
位运算 + 贪心
从最高位开始,贪心地尝试构造最大异或值。
def findMaximumXOR(nums: list[int]) -> int:
result = 0
mask = 0
for i in range(30, -1, -1):
mask |= (1 << i)
# 当前所有数的前缀
prefixes = set(num & mask for num in nums)
# 尝试把结果的这一位置 1
candidate = result | (1 << i)
# 检查是否存在两个前缀异或等于 candidate
for p in prefixes:
if p ^ candidate in prefixes:
result = candidate
break
return result
复杂度:时间 O(31 × n),空间 O(n)
四、状态压缩
思想
用整数的二进制位表示状态集合。
例如:有 5 个任务,每个任务完成/未完成,可以用 5 位二进制表示。
例题:LC 864. 获取所有钥匙的最短路径
# 状态压缩 BFS
# state 的第 k 位表示是否有第 k 把钥匙
五、位运算技巧总结
| 技巧 | 代码 | 用途 |
|---|---|---|
| 判断奇偶 | n & 1 | 1=奇,0=偶 |
| 消除最低 1 | n & (n-1) | 数 1 的个数 |
| 获取最低 1 | n & (-n) | Fenwick 树 |
| 判断 2 的幂 | n & (n-1) == 0 | 只有一个 1 |
| 取第 k 位 | (n >> k) & 1 | 检查位 |
| 设置第 k 位 | n | (1 << k) | 打开位 |
| 清除第 k 位 | n & ~(1 << k) | 关闭位 |
| 切换第 k 位 | n ^ (1 << k) | 翻转位 |
| 交换两数 | a^=b; b^=a; a^=b | 无临时变量 |
六、常见错误与陷阱
错误 1:Python 负数右移
# Python 中负数右移是算术右移(符号位扩展)
-1 >> 1 # 还是 -1
# 解决:用位与限制位数
(-1 >> 1) & 0xFFFFFFFF
错误 2:运算符优先级
# 错误
if n & 1 == 1: # == 优先级高于 &
# 正确
if (n & 1) == 1:
错误 3:忘记括号
# 错误
n & n - 1 # 相当于 n & (n-1),但容易误解
# 正确
n & (n - 1)
错误 4:溢出
# Python 整数无溢出,但其他语言要注意
# 左移可能溢出
七、复盘清单
触发信号
- 唯一/出现次数问题
- 二进制操作
- 状态压缩
- 权限/标志位
技巧选择
- 找唯一数 → 异或
- 数 1 的个数 → n & (n-1)
- 判断 2 的幂 → n & (n-1) == 0
- 位操作 → 掩码
实现检查
- 运算符优先级
- 负数处理
- 边界条件(0、-1)
八、题目清单
基础题
| 题号 | 题目 | 难度 | 考点 |
|---|---|---|---|
| 136 | 只出现一次的数字 | Easy | 异或 |
| 191 | 位 1 的个数 | Easy | n&(n-1) |
| 231 | 2 的幂 | Easy | n&(n-1) |
| 268 | 丢失的数字 | Easy | 异或 |
| 461 | 汉明距离 | Easy | 异或 |
进阶题
| 题号 | 题目 | 难度 | 考点 |
|---|---|---|---|
| 338 | 比特位计数 | Medium | DP |
| 190 | 颠倒二进制位 | Easy | 位操作 |
| 421 | 最大异或值 | Medium | 贪心 |
| 371 | 两整数之和 | Medium | 位运算加法 |
| 78 | 子集 | Medium | 位枚举 |