常见位运算总结
-  
基础位运算
- << >> ~
 - 与&:有0就是0
 - 或|:有1就是1
 - 异或^:相同为0,相异为1 / 无进位相加

 
 -  
给一个数n,确定他的二进制表示中的第x位是0还是1
- 让第x位与上1即可
 - 先让n右移x位
 - &上一个1(仅需考虑最低位,即最右边),所以与上1(0000001,只有最低位是1)

 
 -  
将一个数n的二进制表示的第x位修改成1
- 和1进行或运算|,这样其他位置上的数字保持不变,只需要让第x位或1就行
 - 先让1左移x位,然后和1进行或等运算
 
 

-  
将一个数n的二进制表示的第x位修改成0
- 让第x位与上一个0即可,使其余位保持不变(决定权在上面自身)
 - 让1左移x位后,按位取反
 - n与等上这样一个数

 
 -  
位图的思想
- 本质是一个哈希表,大多情况是数组的形式方便增删查改,可以用O(1)的时间复杂度来查找
 - 现在用一个变量的二进制位记录信息。存储0表示一种信息,存储1表示一种信息。此时用一个变量我们就能实现增删查改。这里我们哈希表是一个变量,然后用变量中某一位的比特位的0、1来帮助我们记录信息。(2、3、4都是为位图服务的)

 
 -  
提取一个数(n)二进制表示中最右侧的1(该操作又称为lowbit,即把最低位的1提取出来)
- 即过这个操作后,
上面的数变为下面的数 - n按位与-n即可(负数是按位取反再加1)

 
 - 即过这个操作后,
 -  
干掉一个数n二进制表示中最右侧的1
- n &(n-1)
 - n-1本质上是当n从最右侧开始出现连续的0,做减法时会一直借位,一直借到最右边的1.即以最右侧的1作为分界线,让他右边的区域都变成相反数

 - 然后再与&上n
就能达到干掉最右侧的1的效果 
 -  
位运算的优先级:能加括号就加括号
 -  
异或(^)运算的运算律
- a ^ 0 = a
 - a ^ a = 0 (消消乐)
 - a ^ b ^ c = a ^ (b ^ c)
 
 
一大堆数随便异或(不考虑运算顺序),结果是唯一的
运用6、7
LeetCode191. 位1的个数
LeetCode318. 比特位计数
LeetCode461. 汉明距离
运用9
LeetCode136. 只出现一次的数字
LeetCode260. 只出现一次的数字III
判断字符是否唯一
判定字符是否唯一
 
题目解析
- 确定一个字符串 s 的所有字符是否全都不同。
 - s[i]仅包含小写字母
 - 如果你不使用额外的数据结构,会很加分。
 
算法原理
- 解法一:哈希表(哈希数组hash[26]):因为题中已经说明仅包含小写字母,判断字符是否在哈希表里,如果不在放进哈希表,移动下一个位置。时间复杂度O(n),空间复杂度O(n)
 - 位图思想:
 

- 优化点:鸽巢原理:因为一共有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] 这个范围内没有出现在数组中的那个数。
 
算法原理
- hash表:创建一个大小为n+1的数组,这样下标刚好是0~n,然后一一对应去找。时间复杂度O(n),空间复杂度O(n)

 - 高斯求和:求出0~n所有数字的和,然后减去数组中的和,得出的结果就是缺失的结果.时间复杂度O(n),空间复杂度O(1)
 

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

 
代码实现
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;
    }
};
 
 
两整数之和
两整数之和
 
题目解析
不使用 运算符 + 和 - ,计算并返回两整数之和。
算法原理
- 笔试场上,不讲武德:直接return a+b
 - 位运算(异或运算——无进位相加): 
  
- 我们只需要找到进位即可。进位的情况是只有1和1会出现。这样就和按位与&的情况一样。并且进位是进到数字左边的位置上,所以我们还需让其进行左移,这样我们就得到了进位的结果,得到这两个结果之后,让其相加。直到进位全部为0即可
 - 先算出无进位相加的结果,在算出进位的结果。
 - 继续(无进位)“相加”两个结果,直至进位全部变为0.
 
 

 
代码实现
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)
 
算法原理
- 位图思想: 
  
-  
列出如下某一个比特位的所有情况

 -  
将这些和相加后,%3,我们会发现一个规律:最终模完之后的结果和a(只出现一次的数)保持一致。

 -  
将nums数组中所有的数的比特位第0位统统加起来%3,如果结果是0,不变,结果是1,修改位1;同理最终结果的倒数第二位也是如此,以此类推,直至将32位比特位全部修改完毕。
 
 -  
 
(设要找的数位 ret 。由于整个数组中,需要找的元素只出现了「⼀次」,其余的数都出现的「三次」,因此我们可以根据所有数的「某⼀个⽐特位」的总和 %3 的结果,快速定位到 ret 的「⼀个⽐特位上」的值是0 还是1 。这样,我们通过 ret 的每⼀个⽐特位上的值,就可以将ret 给还原出来 。)
我们如果想得到ret结果其中的某一位时,我们将nums中所有的数的这一位相加起来,再模上一个3(题中所要求的出现三次,如果是出现n次,就模上n),如果是0,就修改成0;如果是1,就修改成1。
代码实现
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;
    }
};
 
 
消失的两个数字
消失的两个数字
 
题目解析
- 用时间复杂度O(n),空间复杂度O(1)来解决。
 - 数组区间n+2
 

 
算法原理
这道题其实是之前“丢失的数字”+“只出现一次的数字III”
- 1~N这里相当于nums数组+a+b(缺失的两个数字——丢失的数字)
 

- 将nums和1~N统称为一个整体,那么问题就转换为有两个数字出现了1次,剩下的一堆数字出现了2次(丢失的数字III)
 
位运算思想:
- 将所有的数字异或在一起,记为tmp,因为出现两次的数字都异或被抵消了,所有tmp=a^b。接下来,我们将a、b两个数字分开
 - 找到tmp中,比特位上为1的那一位。因为a和b这两个数字是不一样的,所以最终异或的结果是不等于0的。所以tmp中他的比特位上一定有一位上是1(记为x)。此时将刚刚所有的数字(nums和1~N)又可以划分为两大类,一类是x位上是0,一类上是x位上是1。在分别让这些数字和1、0进行异或,就能分离出a和b。

 
代码实现
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};
    }
};
                












![排序算法:【选择排序]](https://img-blog.csdnimg.cn/direct/abf8c24ad7a44b54b333ffd9475c20ff.png)





