【LeetCode热题100】位运算

这篇博客先介绍了常见位运算操作,然后记录了关于位运算的几道题,包括判定字符是否唯一、丢失的数字、两整数之和、只出现一次的数字2、消失的两个数字。

在这一部分,我们不妨先来总结一下常见位运算操作:

1.基础位运算

>>是右移操作,<<是左移操作,~是按位取反,&是有0就是0,|是有1就是1,^有两种记忆方法1)相同为0,相异为1 2)无进位相加

2.给一个数n,确定它的二进制表示表示中的第x位是0还是1

我们约定,二进制的最右边为第0位,最左边为第31位,操作是(n>>x)&1,如果结果为0,那么第x位就是0,否则为1。

3.将一个数n的二进制表示的第x位修改成1

也就是第x位改为1,其它位保持不变,操作是 n |= (1<<x)。

4.将一个数n的二进制表示的第x位修改成0

操作是 n &= (~(1<<x)) 。

5.位图的思想

上面的2、3、4就是为位图来服务的,位图的本质是哈希表,让一个int的32个位的0/1来记录信息,利用2、3、4可以查看和修改位图。

6.提取一个数(n)二进制表示中最右侧的1(lowbit操作)

先来说结果,操作是 n&-n。我们需要先来看一下-n的本质:

-n的本质是将最右侧的1,左边的区域全部变成相反,此时相&后,左边区域全部变成0,右边区域本来就是0,所以&完之后只剩下最右侧的1。

7.干掉一个数(n)二进制表示中最右侧的1

先来说结果,操作是 n&(n-1) ,我们需要看一下n-1的本质:让最右侧及其右边全变成相反,左侧不变,这样相&之后,左侧不变,右侧全部变成0,这就把最右侧的1干掉了。

8.位运算的优先级

能加括号就加括号。

利用上面这几条,我们就可以解决LeetCode中的191、338、461这三道题了,

9.异或^运算的运算律

  1. a^a = 0(消消乐)

  2. a^0 = a

3)a^b^c = a^c^b

cpp 复制代码
class Solution {
public:
    bool isUnique(string astr) 
    {
        int bitmap = 0;
        for(auto e:astr)
        {
            int pos = e - 'a';
            if((bitmap>>pos) & 1) return false;
            bitmap |= (1<<pos);
        }
        return true;
    }
};

题目分析 :这道题有很多种解法,这里我们学习两种方法:哈希表和位图。第一种,使用哈希表,遍历整个字符串,将字符串中的每个字符依次放到哈希表中并判断条件,这种方法的时间复杂度和空间复杂度都是O(N)。第二种,使用位图,使用一个int位图,0位表示'a',1位表示'b',依次类推,因此只需要用到0~25位,遍历整个字符串,修改对应的bit,bit位为0表示之前没遇到这个字符,bit位为0表示之前遇到过这个字符。

此外,我们还可以使用鸽巢原理(抽屉原理)进行优化,如果字符长度大于26,说明肯定有重复字符,直接返回false。

cpp 复制代码
class Solution 
{
public:
    int missingNumber(vector<int>& nums) 
    {
        int ret = 0;
        for(int i = 0 ; i <= nums.size() ; i++) ret ^= i;
        for(auto e : nums) ret ^= e;
        return ret;
    }
};

题目分析:这道题我们还是有很多思路的,包括哈希表、求和公式、异或运算。第一种,哈希表,将数组中所有数字依次放到哈希表中,然后看哈希表中缺失哪个数字。第二种,求和公式,计算出0~n之和,然后减去数组中的每个元素,剩下的就是缺失的元素。第三种,异或运算,将数组中每一个元素都异或到一起,然后再异或0~n中的每一个元素,结果就是缺失的数字。

cpp 复制代码
class Solution {
public:
    int getSum(int a, int b) 
    {
        while(b != 0)
        {
            int x = a^b;// x是无进位相加的结果
            int carry = (a&b)<<1;// carry 是进位
            a = x;
            b = carry;
        }
        return a;
    }
};

题目分析:这种直接用加法求和,大概率是用位运算操作,我们画图来看一下具体方法,主要想法是,a^b是无进位相加,a&b<<1可以表示进位,当进位为0时,循环结束。

cpp 复制代码
class Solution {
public:
    int singleNumber(vector<int>& nums) 
    {
        int i = 0;
        int sum = 0;
        int ret = 0;
        while(i <= 31)
        {
            //找到所有数第i位相加的结果
            for(auto e : nums)
            {
                sum += (e>>i)&1;
            }
            if(sum % 3 == 1) ret |= 1<<i;
            sum = 0;
            i++;
        }
        return ret;
    }
};

题目分析:我们将nums中每个元素的某一位全部加到一起,总共有4中情况:3n个0+0、3n个0+1、3n个1+0、3n个1+1。无论哪种情况,都将加和结果%3,最后得到的0、1、0、1就是所求结果的每一个比特位。

扩展:我们可以把这一道题扩展到除了某个元素只出现一次,其他出现n次,只需在将每一位求和后%n即可。

cpp 复制代码
class Solution {
public:
    vector<int> missingTwo(vector<int>& nums) 
    {
        int n = nums.size() + 2;
        int tmp = 0;
        //1.将所有数字异或到tmp上
        for(auto e : nums) tmp ^= e;
        for(int i = 1 ; i <= n ; i++) tmp ^= i;
        //2.找到a、b中的比特位的那一位
        int j = 0;
        for(j = 0 ; j < 32 ; j++)
        {
            if((tmp >> j) & 1) break;
        } 
        //3.按照tmp位分成两组
        int a = 0, b = 0;
        for(auto e : nums)
        {
            if((e >> j) & 1) a ^= e;
            else b ^= e;
        }
        //3.根据j位的不同,将所有的数划分为两类来异或
        for(int i = 1 ; i <= n ; i++)
        {
            if((i >> j) & 1) a ^= i;
            else b ^= i;
        }

        return {a,b};
    }
};

题目分析:这道题依然是用位运算的方式解决,第一步,先将nums中所有的数异或到同一个变量tmp上,然后再把1~N也异或到这个变量上,最后得到的就是缺失的两个数字的异或。第二步,找到tmp中,比特位为1的那一位。第三步,根据x位的不同,将nums中的数字划分成两组,将这两组分别异或到两个变量上,就可以得到缺失的两个数字。

相关推荐
香菜大丸4 分钟前
链表的归并排序
数据结构·算法·链表
jrrz08284 分钟前
LeetCode 热题100(七)【链表】(1)
数据结构·c++·算法·leetcode·链表
oliveira-time16 分钟前
golang学习2
算法
南宫生1 小时前
贪心算法习题其四【力扣】【算法学习day.21】
学习·算法·leetcode·链表·贪心算法
懒惰才能让科技进步2 小时前
从零学习大模型(十二)-----基于梯度的重要性剪枝(Gradient-based Pruning)
人工智能·深度学习·学习·算法·chatgpt·transformer·剪枝
Ni-Guvara2 小时前
函数对象笔记
c++·算法
泉崎3 小时前
11.7比赛总结
数据结构·算法
你好helloworld3 小时前
滑动窗口最大值
数据结构·算法·leetcode
AI街潜水的八角3 小时前
基于C++的决策树C4.5机器学习算法(不调包)
c++·算法·决策树·机器学习
白榆maple4 小时前
(蓝桥杯C/C++)——基础算法(下)
算法