算法

位运算

直接操作二进制位——常数级加速与优雅的集合表示

基本操作与常用技巧

六种基本位运算:AND(&)、OR(|)、XOR(^)、NOT(~)、左移(<<)、右移(>>)。几个最常用的技巧:

常用技巧速查

技巧表达式说明
判断第 k 位是否为 1(x >> k) & 1右移后取最低位
将第 k 位置 1x | (1 << k)OR 置位
将第 k 位清 0x & ~(1 << k)AND 取反清位
取最低置位x & (-x)= x & (~x+1),即 lowbit
消去最低置位x & (x - 1)Brian Kernighan 技巧
判断 2 的幂次x > 0 && (x & (x-1)) == 02 的幂有且只有一个 1 位
交换两数(无临时变量)a^=b; b^=a; a^=b;XOR 交换,仅作了解
// Common bit tricks
int x = 42;
int k = 3;

bool bitK    = (x >> k) & 1;       // check bit k
int  setK    = x | (1 << k);       // set bit k
int  clearK  = x & ~(1 << k);      // clear bit k
int  lowbit  = x & (-x);           // isolate lowest set bit
int  dropLow = x & (x - 1);        // clear lowest set bit
bool isPow2  = x > 0 && !(x & (x-1)); // is power of 2

// Brian Kernighan popcount
int popcount(int n) {
    int cnt = 0;
    while (n) { n &= n - 1; cnt++; }
    return cnt;
}

溢出提示对 int(32 位)做 1<<31 会触发未定义行为,改用 1LL<<31 或 (unsigned)1<<31。

练习:位运算 — Brian Kernighan Popcount代码练习
测试用例(3 个)
用例 1
输入:1 7
期望:3
用例 2
输入:1 0
期望:0
用例 3
输入:1 255
期望:8

XOR 性质与应用

XOR 的三个核心性质:① a ^ a = 0;② a ^ 0 = a;③ 满足交换律与结合律。这使得 XOR 天然适合「消去成对元素、找单一元素」类问题。

// LC 136 — Single Number: XOR all, pairs cancel
int singleNumber(vector<int>& nums) {
    int res = 0;
    for (int x : nums) res ^= x;
    return res;
}

// LC 260 — Two numbers appear once
pair<int,int> singleNumberIII(vector<int>& nums) {
    int xorAll = 0;
    for (int x : nums) xorAll ^= x;
    int bit = xorAll & (-xorAll); // lowest bit where a != b
    int a = 0;
    for (int x : nums)
        if (x & bit) a ^= x;
    return {a, xorAll ^ a};
}

典型题目

  • 136. 只出现一次的数字(全部 XOR,成对抵消,剩余即答案)
  • 260. 只出现一次的数字 III(两个单独数字,用 lowbit 分组)
  • 137. 只出现一次的数字 II(每个位出现 3 次,取模 3 求单独位)
  • 268. 丢失的数字(0..n XOR nums[],成对抵消)

分组技巧260 题有两个只出现一次的数 a、b:先全 XOR 得 a^b,取其 lowbit(a 和 b 在该位的值不同),用该位将所有数分为两组,各组 XOR 即得 a 和 b。

练习:位运算 — 只出现一次的数字代码练习
测试用例(3 个)
用例 1
输入:5 4 1 2 1 2
期望:4
用例 2
输入:3 2 2 1
期望:1
用例 3
输入:1 1
期望:1

位计数

统计一个整数中 1 的个数(popcount)。Brian Kernighan 算法:反复用 x &= (x-1) 消去最低位的 1,循环次数等于 1 的个数。C++20 起可用 __builtin_popcount(x)。

典型题目

  • 191. 位 1 的个数(Hamming 重量)
  • 338. 比特位计数(0..n 所有数的 popcount,dp: bits[i] = bits[i>>1] + (i&1))
  • 201. 数字范围按位与(找 [m,n] 共同前缀,右移直至 m==n)
  • 190. 颠倒二进制位(逐位取并放到对称位置)

内置函数竞赛/面试中可用 __builtin_popcount(x)(GCC 扩展),std::popcount(x)(C++20),或手写 Brian Kernighan 循环。

练习:位运算 — 丢失的数字(XOR)代码练习
测试用例(3 个)
用例 1
输入:3 3 0 1
期望:2
用例 2
输入:1 0
期望:1
用例 3
输入:4 0 1 3 4
期望:2

位掩码 DP

当 n ≤ 20 时,可用整数的二进制位表示子集状态,枚举所有 2^n 个子集做 DP。枚举子集的子集:for(int sub=mask; sub; sub=(sub-1)&mask)。

典型题目

  • 698. 划分为 k 个相等的子集(bitmask + 记忆化)
  • 1494. 并行课程 II(拓扑 + 位掩码 DP)
  • 847. 访问所有节点的最短路(BFS + 状态 = (当前节点, 已访问集合))

复杂度枚举所有子集 O(2^n),枚举所有子集的子集 O(3^n)(因为每个元素有三种状态:在 mask 中且在 sub 中、在 mask 中且不在 sub 中、不在 mask 中)。

练习:位运算 — 枚举子集 XOR代码练习
测试用例(3 个)
用例 1
输入:4 0
期望:4
用例 2
输入:2 0
期望:2
用例 3
输入:3 1
期望:2