神奇的位运算

500 阅读2分钟

1 基础知识

n & (n - 1) 可以去除 n 的位级表示中最低的那一位,例如对于二进制表示 11110100 ,减去 1 得到 11110011,这两个数按位与得到 11110000。n & (-n) 可以得到 n 的位级表示中最低 的那一位,例如对于二进制表示 11110100,取负得到 00001100,这两个数按位与得到 00000100

2 2的次方

首先我们考虑一个数字是不是 2 的(整数)次方:如果一个 n 是 2 的整数次方,那么它 的二进制一定是 0…010…0 这样的形式;考虑到 n − 1 的二进制是 0…001…1,这两个数求按位与 的结果一定是 0。因此如果 n & (n - 1) 为 0,那么这个数是 2 的次方。
如果这个数也是 4 的次方,那二进制表示中 1 的位置必须为奇数位。我们可以把 n 和二进制 的 10101…101(即十进制下的 1431655765)做按位与,如果结果不为 0,那么说明这个数是 4 的 次方。

 bool isPowerOfFour(int n) {
    return n > 0 && !(n & (n - 1)) && (n & 1431655765);
}

3 二进制翻转

给定一个十进制整数,输出它在二进制下的翻转结果。

uint32_t reverseBits(uint32_t n) {
    uint32_t ans = 0;
    for (int i = 0; i < 32; ++i) {
       ans <<= 1;
       ans += n & 1;
       n >>= 1;
}
return ans; }

4 字符串

给定多个字母串,求其中任意两个字母串的长度乘积的最大值,且这两个字母串不能含有相 同字母。
怎样快速判断两个字母串是否含有重复数字呢?可以为每个字母串建立一个长度为 26 的二 进制数字,每个位置表示是否存在该字母。如果两个字母串含有重复数字,那它们的二进制表示 的按位与不为 0。同时,我们可以建立一个哈希表来存储字母串(在数组的位置)到二进制数字 的映射关系,方便查找调用。

int maxProduct(vector<string>& words) {
    unordered_map<int, int> hash;
    int ans = 0;
    for (const string & word : words) {
       int mask = 0, size = word.size();
       for (const char & c : word) {
           mask |= 1 << (c - ’a’);
       }
       hash[mask] = max(hash[mask], size);
       for (const auto& [h_mask, h_len]: hash) {
           if (!(mask & h_mask)) {
              ans = max(ans, size * h_len);
} }
}
return ans; }

bit计数

本题可以利用动态规划和位运算进行快速的求解。定义一个数组 dp,其中 dp[i] 表示数字 i 的二进制含有 1 的个数。对于第 i 个数字,如果它二进制的最后一位为 1,那么它含有 1 的个数 则为 dp[i-1] + 1;如果它二进制的最后一位为 0,那么它含有 1 的个数和其算术右移结果相同,即 dp[i>>1]。

vector<int> countBits(int num) {
    vector<int> dp(num+1, 0);
    for (int i = 1; i <= num; ++i)
       dp[i] = i & 1? dp[i-1] + 1: dp[i>>1];
// 等价于dp[i] = dp[i&(i-1)] + 1; return dp;
}