常见位运算总结
-
基础位运算
- << >> ~
- 与&:有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};
}
};