算法——位运算

常见位运算总结

  1. 基础位运算

    1. << >> ~
    2. 与&:有0就是0
    3. 或|:有1就是1
    4. 异或^:相同为0,相异为1 / 无进位相加算法——位运算_第1张图片
  2. 给一个数n,确定他的二进制表示中的第x位是0还是1

    1. 让第x位与上1即可
    2. 先让n右移x位
    3. &上一个1(仅需考虑最低位,即最右边),所以与上1(0000001,只有最低位是1)算法——位运算_第2张图片
  3. 将一个数n的二进制表示的第x位修改成1

    1. 和1进行或运算|,这样其他位置上的数字保持不变,只需要让第x位或1就行
    2. 先让1左移x位,然后和1进行或等运算

算法——位运算_第3张图片

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

    1. 让第x位与上一个0即可,使其余位保持不变(决定权在上面自身)
    2. 让1左移x位后,按位取反
    3. n与等上这样一个数算法——位运算_第4张图片
  2. 位图的思想

    1. 本质是一个哈希表,大多情况是数组的形式方便增删查改,可以用O(1)的时间复杂度来查找
    2. 现在用一个变量的二进制位记录信息。存储0表示一种信息,存储1表示一种信息。此时用一个变量我们就能实现增删查改。这里我们哈希表是一个变量,然后用变量中某一位的比特位的0、1来帮助我们记录信息。(2、3、4都是为位图服务的)算法——位运算_第5张图片
  3. 提取一个数(n)二进制表示中最右侧的1(该操作又称为lowbit,即把最低位的1提取出来)

    1. 即过这个操作后,算法——位运算_第6张图片上面的数变为下面的数
    2. n按位与-n即可(负数是按位取反再加1)算法——位运算_第7张图片
  4. 干掉一个数n二进制表示中最右侧的1

    1. n &(n-1)
    2. n-1本质上是当n从最右侧开始出现连续的0,做减法时会一直借位,一直借到最右边的1.即以最右侧的1作为分界线,让他右边的区域都变成相反数image.jpeg
    3. 然后再与&上n算法——位运算_第8张图片就能达到干掉最右侧的1的效果
  5. 位运算的优先级:能加括号就加括号

  6. 异或(^)运算的运算律

    1. a ^ 0 = a
    2. a ^ a = 0 (消消乐)
    3. a ^ b ^ c = a ^ (b ^ c)

一大堆数随便异或(不考虑运算顺序),结果是唯一的算法——位运算_第9张图片
运用6、7
LeetCode191. 位1的个数
LeetCode318. 比特位计数
LeetCode461. 汉明距离
运用9
LeetCode136. 只出现一次的数字
LeetCode260. 只出现一次的数字III

判断字符是否唯一

判定字符是否唯一

题目解析

  • 确定一个字符串 s 的所有字符是否全都不同。
  • s[i]仅包含小写字母
  • 如果你不使用额外的数据结构,会很加分。

算法原理

  1. 解法一:哈希表(哈希数组hash[26]):因为题中已经说明仅包含小写字母,判断字符是否在哈希表里,如果不在放进哈希表,移动下一个位置。时间复杂度O(n),空间复杂度O(n)
  2. 位图思想:

算法——位运算_第10张图片

  1. 优化点:鸽巢原理:因为一共有26个英文字母,当字符串的长度大于26时,一定会有重复的字符

代码实现

class Solution {
public:
    bool isUnique(string astr) {
        // 利⽤鸽巢原理来做的优化
        if(astr.size() > 26) return false;
        int bitMap = 0;
        for(auto ch : astr)
        {
            int i = ch - 'a';
        // 先判断字符是否已经出现过
        if(((bitMap >> i) & 1) == 1) return false;
        
        // 把当前字符加⼊到位图中
        bitMap |= 1 << i;
        }
            return true;
    }
};

丢失的数字

丢失的数字

题目解析

给定一个包含 [0, n] 中 n 个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。

算法原理

  1. hash表:创建一个大小为n+1的数组,这样下标刚好是0~n,然后一一对应去找。时间复杂度O(n),空间复杂度O(n)算法——位运算_第11张图片
  2. 高斯求和:求出0~n所有数字的和,然后减去数组中的和,得出的结果就是缺失的结果.时间复杂度O(n),空间复杂度O(1)

算法——位运算_第12张图片

  1. 位运算(异或运算的运算律):将数组中的数和0n全部进行异或,所有重复的数异或结果都是0,0n数剩下的一个和数组中的0异或(数组缺失),得出的结果就是缺失的。时间复杂度O(n),空间复杂度O(1)。算法——位运算_第13张图片

代码实现

class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int ret = 0;
        for(auto x : nums) ret ^= x;
        for(int i = 0; i <= nums.size(); i++) ret ^= i;
        return ret;
    }
};

两整数之和

两整数之和

题目解析

不使用 运算符 + 和 - ,计算并返回两整数之和。

算法原理

  1. 笔试场上,不讲武德:直接return a+b
  2. 位运算(异或运算——无进位相加):
    1. 我们只需要找到进位即可。进位的情况是只有1和1会出现。这样就和按位与&的情况一样。并且进位是进到数字左边的位置上,所以我们还需让其进行左移,这样我们就得到了进位的结果,得到这两个结果之后,让其相加。直到进位全部为0即可
    2. 先算出无进位相加的结果,在算出进位的结果。
    3. 继续(无进位)“相加”两个结果,直至进位全部变为0.

算法——位运算_第14张图片

代码实现

class Solution {
public:
    int getSum(int a, int b) 
    {
        while(b != 0)
        {
            int x = a ^ b; // 先算出⽆进位相加的结果
            //这里排除-1这种情况,-1二进制全是1
            unsigned int carry = (unsigned int)(a & b) << 1; // 算出进位  
            a = x;
            b = carry;
        }
            return a;
    }
};

只出现一次的数字II

只出现一次的数字II

题目解析

  • 给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 **三次 。**请你找出并返回那个只出现了一次的元素。
  • 要求时间复杂度O(n),空间复杂度O(1)

算法原理

  1. 位图思想:
    1. 列出如下某一个比特位的所有情况算法——位运算_第15张图片

    2. 将这些和相加后,%3,我们会发现一个规律:最终模完之后的结果和a(只出现一次的数)保持一致。算法——位运算_第16张图片

    3. 将nums数组中所有的数的比特位第0位统统加起来%3,如果结果是0,不变,结果是1,修改位1;同理最终结果的倒数第二位也是如此,以此类推,直至将32位比特位全部修改完毕。

(设要找的数位 ret 。由于整个数组中,需要找的元素只出现了「⼀次」,其余的数都出现的「三次」,因此我们可以根据所有数的「某⼀个⽐特位」的总和 %3 的结果,快速定位到 ret 的「⼀个⽐特位上」的值是0 还是1 。这样,我们通过 ret 的每⼀个⽐特位上的值,就可以将ret 给还原出来 。)

我们如果想得到ret结果其中的某一位时,我们将nums中所有的数的这一位相加起来,再模上一个3(题中所要求的出现三次,如果是出现n次,就模上n),如果是0,就修改成0;如果是1,就修改成1。算法——位运算_第17张图片

代码实现

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ret = 0;
        for(int i = 0; i < 32; i++) // 依次去修改 ret 中的每⼀位
        {
            int sum = 0;
            for(int x : nums) // 计算nums中所有的数的第 i 位的和
            if(((x >> i) & 1) == 1)
            sum++;
            sum %= 3;
        if(sum == 1) ret |= 1 << i;
        }
        return ret;
    }
};

消失的两个数字

消失的两个数字

题目解析

  1. 用时间复杂度O(n),空间复杂度O(1)来解决。
  2. 数组区间n+2

算法——位运算_第18张图片

算法原理

这道题其实是之前“丢失的数字”+“只出现一次的数字III

  1. 1~N这里相当于nums数组+a+b(缺失的两个数字——丢失的数字

算法——位运算_第19张图片

  1. 将nums和1~N统称为一个整体,那么问题就转换为有两个数字出现了1次,剩下的一堆数字出现了2次(丢失的数字III

位运算思想:

  1. 将所有的数字异或在一起,记为tmp,因为出现两次的数字都异或被抵消了,所有tmp=a^b。接下来,我们将a、b两个数字分开
  2. 找到tmp中,比特位上为1的那一位。因为a和b这两个数字是不一样的,所以最终异或的结果是不等于0的。所以tmp中他的比特位上一定有一位上是1(记为x)。此时将刚刚所有的数字(nums和1~N)又可以划分为两大类,一类是x位上是0,一类上是x位上是1。在分别让这些数字和1、0进行异或,就能分离出a和b。算法——位运算_第20张图片

代码实现

class Solution {
public:
    vector<int> missingTwo(vector<int>& nums) {
        // 1. 将所有的数异或在⼀起
        int tmp = 0;
        for(auto x : nums) tmp ^= x;
        for(int i = 1; i <= nums.size() + 2; i++) tmp ^= i;
        
        // 2. 找出 a,b 中⽐特位不同的那⼀位
        int diff = 0;

        while(1)
        {
            //因为一定会有一个比特位上是1,所以一定回跳出这个死循环
            if(((tmp >> diff) & 1) == 1) break;
            else diff++;
        }
        // 3. 根据 diff 位的不同,将所有的数划分为两类来异或
        int a = 0, b = 0;
        for(int x : nums)
            if(((x >> diff) & 1) == 1) b ^= x;
            else a ^= x;

        for(int i = 1; i <= nums.size() + 2; i++)
            if(((i >> diff) & 1) == 1) b ^= i;
            else a ^= i;
        
            return {a,b};
    }
};

你可能感兴趣的:(算法,算法)