LeetCode力扣刷题——神奇的位运算

位运算


一、常用技巧

        位运算是算法题里比较特殊的一种类型,它们利用二进制位运算的特性进行一些奇妙的优化和计算。常用的位运算符号包括:“∧ ”按位异或、“ & ”按位与、“|”按位或、“ ”取反、“ << ” 算术左移和“>> ”算术右移。以下是一些常见的位运算特性,其中 0s 1s 分别表示只由 0 1 构成的二进制数字。
x ^ 0s = x     x & 0s = 0     x | 0s = x
x ^ 1s = ~x    x & 1s = x     x | 1s = 1s
x ^ x = 0      x & x = x      x | x = x
        除此之外,n & (n - 1) 可以去除 n 的位级表示中最低的那一位,例如对于二进制表示 11110100 ,减去 1 得到 11110011 ,这两个数按位与得到 11110000 n & (-n) 可以得到 n 的位级表示中最低的那一位,例如对于二进制表示 11110100 ,取负得到 00001100 ,这两个数按位与得到 00000100 。还有更多的并不常用的技巧,若读者感兴趣可以自行研究,这里不再赘述。

二、经典问题

1. 位运算基础问题

461. 汉明距离

461. Hamming Distance

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

        给你两个整数 x 和 y,计算并返回它们之间的汉明距离。

        对两个数进行按位异或操作,统计有多少个 1 即可。
class Solution {
public:
    int hammingDistance(int x, int y) {
        int diff = x ^ y, ans = 0;
        while(diff){
            ans += diff & 1;
            diff >>= 1;
        }
        return ans;
    }
};

190. 颠倒二进制位

190. Reverse Bits

        颠倒给定的 32 位无符号整数的二进制位。

提示:

        请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
        在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在 示例 2 中,输入表示有符号整数 -3,输出表示有符号整数 -1073741825。

        使用算术左移和右移,可以很轻易地实现二进制的翻转。
class Solution {
public:
    uint32_t reverseBits(uint32_t n) {
        uint32_t ans = 0;
        for(int i=0; i<32; ++i){
            // ans左移一位留出位置
            ans <<= 1;
            // 将n的最后一位放到ans留出的末尾
            ans += n & 1;
            // 将n最后一位移出
            n >>= 1;
        }
        return ans;
    }
};

136. 只出现一次的数字

136. Single Number

        给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

        说明:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

        我们可以利用 x x = 0 x 0 = x 的特点,将数组内所有的数字进行按位异或。出现两次 的所有数字按位异或的结果是 0 0 与出现一次的数字异或可以得到这个数字本身。
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ans = 0;
        for(const int & num: nums){
            ans ^= num;
        }
        return ans;
    }
};

2. 二进制特性

        利用二进制的一些特性,我们可以把位运算使用到更多问题上。
        例如,我们可以利用二进制和位运算输出一个数组的所有子集。假设我们有一个长度为 n 的数组,我们可以生成长度为 n 的所有二进制, 1 表示选取该数字, 0 表示不选取。这样我们就获得了 2^ n 个子集。

342. 4的幂

342. Power of Four

        给定一个整数,写一个函数来判断它是否是 4 的幂次方。如果是,返回 true ;否则,返回 false 。

        整数 n 是 4 的幂次方需满足:存在整数 x 使得 n == 4^x

        首先我们考虑一个数字是不是 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 的次方。
class Solution {
public:
    bool isPowerOfFour(int n) {
        return n > 0 && !(n & n - 1) && (n & 1431655765);
    }
};

318. 最大单词长度乘积

318. Maximum Product of Word Lengths

        给你一个字符串数组 words ,找出并返回 length(words[i]) * length(words[j]) 的最大值,并且这两个单词不含有公共字母。如果不存在这样的两个单词,返回 0 。

        怎样快速判断两个字母串是否含有重复数字呢?可以为每个字母串建立一个长度为 26 的二进制数字,每个位置表示是否存在该字母。如果两个字母串含有重复数字,那它们的二进制表示的按位与不为 0 。同时,我们可以建立一个哈希表来存储字母串(在数组的位置)到二进制数字的映射关系,方便查找调用。
class Solution {
public:
    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;
    }
};

338. 比特位计数

338. Counting Bits

        给你一个整数 n ,对于 0 <= i <= n 中的每个 i ,计算其二进制表示中 1 的个数 ,返回一个长度为 n + 1 的数组 ans 作为答案。

        本题可以利用动态规划和位运算进行快速的求解。定义一个数组 dp ,其中 dp[i] 表示数字 i 的二进制含有 1 的个数。对于第 i 个数字,如果它二进制的最后一位为 1 ,那么它含有 1 的个数 则为 dp[i-1] + 1 ;如果它二进制的最后一位为 0 ,那么它含有 1 的个数和其算术右移结果相同,即 dp[i>> 1]
class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> dp(n + 1, 0);
        for(int i=1; i<=n; ++i){
            dp[i] = i & 1? dp[i - 1] + 1: dp[i>>1];
        }
        return dp;
    }
};

三、巩固练习

268. 丢失的数字

268. Missing Number

693. 交替位二进制数

693. Binary Number with Alternating Bits

476. 数字的补数

476. Number Complement

260. 只出现一次的数字 III

260. Single Number III


欢迎大家共同学习和纠正指教

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值