⚡ 位运算 — 二进制的微观世界

从位的角度看数据,发现O(1)的魔法操作

📖 位运算的核心概念

位运算是直接操作二进制位的技巧,可以将某些操作从 O(n) 优化到 O(1) 或 O(log n)。面试中常考:位计数、位掩码、异或技巧。理解位运算的关键是把数字看作二进制串

Python 位运算操作符: 运算符 名称 示例 说明 ────────────────────────────────────────────── & 按位与 12 & 5 = 4 1100 & 0101 = 0100 | 按位或 12 | 5 = 13 1100 | 0101 = 1101 ^ 按位异或 12 ^ 5 = 9 1100 ^ 0101 = 1001 ~ 按位取反 ~5 = -6 取反+1(补码) << 左移 3 << 2 = 12 3 × 2² = 12 >> 右移 12 >> 2 = 3 12 ÷ 2² = 3 异或性质(核心): a ^ a = 0 (自反性) a ^ 0 = a (恒等性) a ^ b ^ a = b (交换律+自反性)

1. 位运算常见技巧

判断奇偶:n & 1 — 最低位为1是奇数,为0是偶数。比 n % 2 更快。
清除最低位的1:n & (n - 1) — 这是最经典的位运算技巧!例如 1010 & 1001 = 1000,最低位的1被清除了。
获取最低位的1:n & (-n) — 利用补码特性。例如 1010 & 0110 = 0010,只保留最低位的1。

2. Python 位运算注意事项

# Python 整数无限精度,注意负数右移
-1 >> 1  # → -1 (算术右移,高位补1)

# 获取32位无符号值
n & 0xFFFFFFFF

# 二进制表示
bin(10)  # → '0b1010'
format(10, '032b')  # → '00000000000000000000000000001010'

# 位计数
bin(10).count('1')  # → 2 (但面试不能直接用)
n & (n-1) 是位运算最重要的技巧!它可以:①清除最低位的1(计数1的个数);②判断是否是2的幂(只有1个1);③在位掩码中遍历子集。务必牢记!

🎯 题目一:位1的个数 (LC 191)

题目描述:给定一个无符号整数,返回其二进制表达式中1的个数(也称作汉明权重)。

示例: n = 11 (二进制 1011) 方法: n & (n-1) 清除最低位的1 1011 & 1010 = 1010 (清除第0位) count=1 1010 & 1001 = 1000 (清除第1位) count=2 1000 & 0111 = 0000 (清除第3位) count=3 结果: 3 个1

思路分析

n & (n-1) 计数法:每次执行 n &= n - 1 清除一个1,直到 n 变为0。执行次数就是1的个数。比逐位检查更高效,因为只遍历1的个数次。
逐位检查法:检查每一位是否为1,需要遍历32次(32位整数)。count += n & 1; n >>= 1

代码实现

class Solution:
    def hammingWeight(self, n: int) -> int:
        count = 0
        while n:
            n &= n - 1  # 清除最低位的1
            count += 1
        return count

复杂度分析

时间复杂度:O(k),k 为 1 的个数(最多32)

空间复杂度:O(1)

LC 191 在 Python 中需要注意:如果 n 是负数,需要先转成32位无符号表示。题目说是无符号整数,但 Python 没有无符号类型,用 n & 0xFFFFFFFF 处理。

🎯 题目二:比特位计数 (LC 338)

题目描述:给定整数 n,对于 0 ≤ i ≤ n 的每个 i,计算其二进制中1的个数,返回数组。

示例: n = 5 0 → 0000 → 0个1 1 → 0001 → 1个1 2 → 0010 → 1个1 3 → 0011 → 2个1 4 → 0100 → 1个1 5 → 0101 → 2个1 结果: [0,1,1,2,1,2] DP规律: dp[i] = dp[i>>1] + (i&1) 即: 去掉最低位后的1的个数 + 最低位是否为1 5 = 101 → dp[5] = dp[2] + 1 = 1 + 1 = 2 ✓ 4 = 100 → dp[4] = dp[2] + 0 = 1 + 0 = 1 ✓

思路分析

动态规划:dp[i] = dp[i >> 1] + (i & 1)。i 右移一位等价于去掉最低位,dp[i>>1] 已经计算好了去掉最低位后的1的个数,再加上最低位是否为1即可。
另一种公式:dp[i] = dp[i & (i-1)] + 1,利用 i & (i-1) 清除最低位的1。

代码实现

class Solution:
    def countBits(self, n: int) -> List[int]:
        dp = [0] * (n + 1)
        for i in range(1, n + 1):
            dp[i] = dp[i >> 1] + (i & 1)
        return dp

复杂度分析

时间复杂度:O(n)

空间复杂度:O(n)

LC 338 要求 O(n) 时间,不能对每个数单独调用 LC 191 的解法(那样是 O(n log n))。DP 的关键是发现相邻数的二进制关系。

🎯 题目三:汉明距离 (LC 461)

题目描述:两个整数之间的汉明距离是这两个数对应二进制位不同的位置数目。

示例: x = 1, y = 4 x = 0 0 0 1 y = 0 1 0 0 ^ ^ ^ ← 不同的位 异或: 0 1 0 1 → 2个1 → 汉明距离 = 2 公式: hamming(x, y) = popcount(x ^ y) 即: 异或后统计1的个数

思路分析

异或 + 计数:异或操作使相同位变0、不同位变1,然后统计1的个数即为汉明距离。利用 LC 191 的 n & (n-1) 技巧计数。

代码实现

class Solution:
    def hammingDistance(self, x: int, y: int) -> int:
        xor = x ^ y
        count = 0
        while xor:
            xor &= xor - 1
            count += 1
        return count

复杂度分析

时间复杂度:O(k),k 为汉明距离

空间复杂度:O(1)

汉明距离在信息论、编码理论中广泛应用。LC 477 汉明距离总和是进阶版:求数组中所有数对的汉明距离之和,需要逐位统计0和1的个数。

🔬 位运算高级技巧

1. 判断2的幂

# 2的幂只有一个1
def is_power_of_two(n):
    return n > 0 and (n & (n - 1)) == 0

# 原理: 8 = 1000, 7 = 0111
# 8 & 7 = 0 → 2的幂
# 6 = 0110, 5 = 0101
# 6 & 5 = 0100 ≠ 0 → 不是2的幂

2. 交换两个数(不用临时变量)

a ^= b
b ^= a
a ^= b
# 原理: a^b^b = a, a^b^a = b
# 面试炫技用,实际代码别这么写(可读性差)

3. 位掩码枚举子集

# 枚举集合 {0,1,...,n-1} 的所有子集
n = 3
for mask in range(1 << n):
    subset = [i for i in range(n) if mask & (1 << i)]
    print(f"mask={mask:03b}, subset={subset}")

# 输出:
# mask=000, subset=[]
# mask=001, subset=[0]
# mask=010, subset=[1]
# mask=011, subset=[0,1]
# mask=100, subset=[2]
# mask=101, subset=[0,2]
# mask=110, subset=[1,2]
# mask=111, subset=[0,1,2]

4. 只出现一次的数字

# LC 136: 数组中只有一个数出现一次,其余出现两次
def single_number(nums):
    result = 0
    for num in nums:
        result ^= num
    return result

# 原理: a^a=0, 所有成对的数异或抵消
# 剩下的就是只出现一次的数
位掩码枚举子集是状态压缩 DP 的基础。当 n ≤ 20 时,可以用一个整数表示集合,位运算操作子集,比 set 更快更省空间。
成就解锁:位运算大师 — 掌握n&(n-1)计数、DP位计数、异或距离三大核心技巧
LeetCode AC验证:LC 191 Number of 1 Bits ✅ | LC 338 Counting Bits ✅ | LC 461 Hamming Distance ✅

📝 课后练习

  1. LC 136 只出现一次的数字(异或抵消)
  2. LC 137 只出现一次的数字 II(位计数)
  3. LC 477 汉明距离总和(逐位统计)
  4. LC 201 数字范围按位与(找公共前缀)
  5. LC 421 数组中两个数的最大异或值(位+Trie)

🔑 本课要点回顾

📚 扩展阅读

思考题:如何用位运算实现两个32位整数的加法(不使用+运算符)?提示:异或得到不含进位的和,与运算后左移一位得到进位,循环直到进位为0。