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& 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& words) {
        unordered_map 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 countBits(int n) {
        vector 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


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

你可能感兴趣的:(LeetCode,数据结构与算法——经典题目,每日一练:经典算法题,c语言,c++,leetcode,算法,数据结构)