Notes

位运算 (Bit Manipulation)

核心技巧速查

#define GET_BIT(n, i) (((n) & (1LL << ((i)-1))) >> ((i)-1)) // i start from 1
#define SET_BIT(n, i) ((n) | (1LL << ((i)-1)))
#define CLR_BIT(n, i) ((n) & ~(1LL << ((i)-1)))

// 2's complement: -x == (~x) + 1
// lowbit: 只保留最低位的 1
auto lowbit = [](int x) { return x & (-x); }

// highbit: 只保留最高位的 1(延迟更新技巧)
int highbit(int x) {
    int res = 0;
    for (int i = x; i != 0; i -= lowbit(i)) res = i;
    return res;
}

// 掩码取补(忽略前导 0):00000101 -> 00000010
int maskedComplement(int x) {
    int mask = highbit(x) - 1;
    return (~x) & mask;
}

// 去掉最低位的 1
n = n & (n - 1);

// 大小写转换
('a' | ' ') == 'a';   ('A' | ' ') == 'a';
('b' & '_') == 'B';   ('B' & '_') == 'B';
('d' ^ ' ') == 'D';   ('D' ^ ' ') == 'd';
c++

应用:判 2 的幂 (n & (n-1)) == 0;数 1 的个数(while (n) { n &= n - 1; ans++; });异或找只出现一次的元素。

位运算加法模拟(不用 +):

int add(int a, int b) {
    while (b) {
        unsigned int c = (unsigned int)(a & b) << 1; // to handle negatives
        a ^= b;
        b = c;
    }
    return a;
}
c++

状态压缩

用二进制位表示"集合/状态"(元素 ≤ 30 时)。典型:把单词的字母集合压成 26 位掩码;BFS 状态里带上访问集合(见 09_bfs_dfs 的 847 题)。

例题

数组中只出现一次的数字系列

一个出现一次、其余两次:全部异或。

两个出现一次、其余两次剑指 Offer 56-I):全部异或得 a^b,任选其中为 1 的一位做分组依据:

class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        int a = 0, b = 0, i = 0;
        for (int x: nums) a ^= x;
        while ((a & (1<<i)) == 0) i++;
        a = 0;
        for (int x: nums) {
            if (x & (1<<i)) a ^= x;
            else b ^= x;
        }
        return {a, b};
    }
};
cpp

一个出现一次、其余出现三次剑指 Offer 56-II):逐位统计出现次数模 3;通解(出现 m 次)模 m:

class Solution {
public:
    int singleNumber(vector<int>& nums, int m = 3) { // 通解:其余数字出现 m 次
        int v[32] = {0};
        for (int x: nums) {
            for (int i = 0; i < 32; i++) {
                if (x & (1 << i)) v[i] = (v[i] + 1) % m;
            }
        }
        int ans = 0;
        for (int i = 0; i < 32; i++) {
            if (v[i]) ans |= (1 << i);
        }
        return ans;
    }
};
cpp

869 重新排序得到 2 的幂

词频统计:统计 n 的十进制数字词频,与某个 2 的幂(共 30 个)比对:

class Solution {
public:
    bool check(vector<int> &num, long n) {
        vector<int> v(10);
        while(n > 0) {
            v[n % 10]++;
            n /= 10;
        }
        for(int i = 0; i < 10; i++) {
            if(v[i] != num[i]) return false;
        }
        return true;
    }

    bool reorderedPowerOf2(int n) {
        vector<int> num(10);
        while(n > 0) {
            num[n % 10]++;
            n /= 10;
        }
        for(int i = 0; i < 30; i++) {
            if(check(num, (1l << i))) return true;
        }
        return false;
    }
};
cpp

318 最大单词长度乘积

用 26 位掩码表示单词的字母集合,O(1) 判断两词是否含公共字母:

class Solution {
public:
    int maxProduct(vector<string>& words) {
        unordered_map<int,int> map;
        for (int i = 0; i < words.size(); i++) {
            int mask = 0;
            for (char c: words[i]) {
                mask |= 1 << (c - 'a');
            }
            if (map.count(mask)) {
                map[mask] = max(map[mask], (int)words[i].size());
            } else {
                map[mask] = words[i].size();
            }
        }
        int maxProd = 0;
        for (auto [mask1, len1] : map) {
            for (auto [mask2, len2] : map) {
                if ((mask1 & mask2) == 0) {
                    maxProd = max(maxProd, len1 * len2);
                }
            }
        }
        return maxProd;
    }
};
cpp

相关

  • 数位 DP(不含连续一的非负整数,基于位与斐波那契):07_dynamic_programming
  • lowbit 是树状数组的基石:17_binary_indexed_tree
  • 状态压缩 BFS(847 访问所有节点的最短路径):09_bfs_dfs

Type to search.