专题五:位运算

84 阅读5分钟

0 常见位运算总结

0.1 基础位运算

<<、 >>、 ~、 &、 |、 ^(无进位加法)

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

(n >> x) & 1

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

n |= (1 << x)

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

n &= (~(1 << x))

0.5 位图思想

0.6 提取一个数(n) 二进制表示中最右侧的1

n ~ (-n)

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

n &(n - 1)

0.8 位运算的优先级

能加括号就加括号

0.9 异或(^) 运算的运算律

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

1 判定字符是否唯一

1.1 题目链接

面试题 01.01. 判定字符是否唯一

1.2 题目描述

实现一个算法,确定一个字符串 s 的所有字符是否全都不同。

示例 1:

输入: s = "leetcode"
输出: false 

示例 2:

输入: s = "abc"
输出: true

1.3 解法(位图的思想):

算法思路

利⽤位图的思想,每⼀个⽐特位代表⼀个字符,⼀个 int 类型的变量的 32 位⾜够表⽰所有的⼩写字⺟。⽐特位⾥⾯如果是 0 ,表⽰这个字符没有出现过。⽐特位⾥⾯的值是 1,表⽰该字符出现过。

那么我们就可以⽤⼀个整数来充当哈希表

1.4 C++算法代码:

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;
    }
};

2 丢失的数字

2.1 题目链接

268. 丢失的数字

2.2 题目描述

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

 

示例 1:

输入: nums = [3,0,1]
输出: 2
解释: n = 3,因为有 3 个数字,所以所有的数字都在范围 [0,3] 内。2 是丢失的数字,因为它没有出现在 nums 中。

示例 2:

输入: nums = [0,1]
输出: 2
解释: n = 2,因为有 2 个数字,所以所有的数字都在范围 [0,2] 内。2 是丢失的数字,因为它没有出现在 nums 中。

示例 3:

输入: nums = [9,6,4,2,3,5,7,0,1]
输出: 8
解释: n = 9,因为有 9 个数字,所以所有的数字都在范围 [0,9] 内。8 是丢失的数字,因为它没有出现在 nums 中。

示例 4:

输入: nums = [0]
输出: 1
解释: n = 1,因为有 1 个数字,所以所有的数字都在范围 [0,1] 内。1 是丢失的数字,因为它没有出现在 nums 中。

2.3 解法(位运算):

算法思路

设数组的⼤⼩为 n ,那么缺失之前的数就是 [0, n] ,数组中是在 [0, n] 中缺失⼀个数形成的序列。

如果我们把数组中的所有数,以及 [0, n] 中的所有数全部异或在一起,那么根据异或运算的消消乐规律,最终的异或结果应该就是缺失的数~

2.4 C++算法代码:

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

3 两整数之和

3.1 题目链接

371. 两整数之和

3.2 题目描述

给你两个整数 a 和 b ,不使用 运算符 + 和 - ​​​​​​​,计算并返回两整数之和。

 

示例 1:

输入: a = 1, b = 2
输出: 3

示例 2:

输入: a = 2, b = 3
输出: 5

3.3 解法(位运算):

算法思路

  • 异或 ^ 运算本质是⽆进位加法
  • 按位与 & 操作能够得到进位
  • 然后⼀直循环进⾏,直到进位变成 0 为⽌。

3.4 C++算法代码:

class Solution {
public:
    int getSum(int a, int b) {
        while(b)
        {
            int x = a ^ b;  // 先算出无进位相加的结果
            int carry = (a & b) << 1;  // 算出进位
            a = x;
            b = carry;
        }
        return a;
    }
};

4 只出现一次的数字 II

4.1 题目链接

137. 只出现一次的数字 II

4.2 题目描述

给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。 请你找出并返回那个只出现了一次的元素。

你必须设计并实现线性时间复杂度的算法且使用常数级空间来解决此问题。

 

示例 1:

输入: nums = [2,2,3,2]
输出: 3

示例 2:

输入: nums = [0,1,0,1,0,1,99]
输出: 99

4.3 解法(⽐特位计数):

算法思路

设要找的数位 ret 。

由于整个数组中,需要找的元素只出现了⼀次,其余的数都出现的三次,因此我们可以根据所有数的某⼀个⽐特位的总和 %3 的结果,快速定位到 ret 的⼀个⽐特位上的值是0 还是 1 。

这样,我们通过 ret 的每⼀个⽐特位上的值,就可以将 ret 给还原出来。

4.4 C++算法代码:

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

5 消失的两个数字

5.1 题目链接

面试题 17.19. 消失的两个数字

5.2 题目描述

给定一个数组,包含从 1 到 N 所有的整数,但其中缺了两个数字。你能在 O(N) 时间内只用 O(1) 的空间找到它们吗?

以任意顺序返回这两个数字均可。

示例 1:

输入: [1]
输出: [2,3]

示例 2:

输入: [2,3]
输出: [1,4]

5.3 解法(位运算):

算法思路

本题就是 268. 丢失的数字 + 260. 只出现⼀次的数字 III 组合起来的题。

先将数组中的数和 [1, n + 2] 区间内的所有数异或在⼀起,问题就变成了:有两个数出现了⼀次,其余所有的数出现了两次。进⽽变成了 260. 只出现⼀次的数字 III 这道题。

5.4 C++算法代码:

class Solution {
public:
    vector<int> missingTwo(vector<int>& nums) {
        // 1. 将所有的数异或在一起 得到 a ^ b
        int tmp = 0;
        for(int e : nums) tmp ^= e;
        for(int i = 1; i <= nums.size() + 2; i++) tmp ^= i;

        // 2. 找出 a、b 中比特位不同的一位
        int diff = 0;
        while(1)
        {
            if(((tmp >> diff) & 1) == 1) break;
            else diff++;
        }

        // 3. 根据diff位的不同,将多有的数划分成两类来异或
        int a = 0, b = 0;
        for(int e : nums)
            if(((e >> diff) & 1) == 1) a ^= e;
            else b ^= e;
        for(int i = 1; i <= nums.size() + 2; i++)
            if(((i >> diff) & 1) == 1) a ^= i;
            else b ^= i;

        return {a,b};
    }
};