关于位运算
①基础位运算
我们前面用到的位运算符有很多,左移(<<),右移(>>),按位取反(~)就不讲了,我们来简单复习下按位与(&),按位或(|),按位异或(^),这三个是刚学位运算时比较容易混淆的三个
- &:有0就是0(0 & 0 = 0,0 & 1 = 0, 1 & 1 = 1)
- |:有1就是1(0 | 0 = 0, 1 & 0 = 1,1 | 1 = 1)
- ^:相同为0,不同为1,或者是无进位相加(0 ^ 0 = 0,1 ^ 0 = 1,1 ^ 1 = 0)
②给一个数n,确定它的二进制表示中的第 x 位是 0 还是 1
假设n是:0 1 1 0 1 0 1 0 0 1,二进制表示中最右边是低位,从右往左开始,一共32位
假设我们要确定上面那个蓝色的位置的位是0还是1(假设蓝色位置的位数我们还不知道),只要将这一位与(&)上一个1就可以了,如果结果为0,表示这位是0;如果结果为1,则表示这位是1
所以我们就要想办法把这个1移到最右边,先n >> x让n右移x位,然后与(&)一个1就可以了
③位运算的优先级
不论是什么操作,能加括号就加括号,以上面的为例,我们要同时计算>>和&,所以直接(n >> x)&1 就可以了
④将一个数n的二进制位的第x位修改成1
还是以上面的为例,n = 0 1 1 0 1 0 1 0 0 1,我们要修改第三位的0为1,其余的为保持不变,只需要将第三位或(|)上一个1,其余为或上0,就OK了:

那么如何搞呢?只需要 n |= (1 << x) 就可以了
⑤将一个n的二进制表示的第x位修改成0
和上面一样的,只要n &= (~(1 << x))

⑥位图的思想
位图我们之前已经讲过了:C++——位图与布隆过滤器_布隆过滤器和位图优缺点-CSDN博客
上面三个操作都是为了位图服务的,位图的本质就是一个哈希表,只不过将数组和链表形式的哈希表变成了比特位表示的哈希表
位图用一个整型变量的比特位里的0和1来记录信息,这就是位图的主要思想,所以会经常用到前面讲的三个操作
⑦提取一个数n的二进制位中最右侧的1
加入n是0 1 1 1 0 1 0 1 0 0 ,其中蓝色1就是最右侧的1,根据题目要求后,n就变成了 0 0 0 0 0 0 0 1 0 0 ,除了最右侧的1,其它的全部变成0,这就是这道题表达的意思
所以,如何提取最右边的1呢?只需要 n & -n 即可,我们先理解下 -n 的含义:

可以看到,n按位取反再+1之后,最右侧的1,左边的区域全部变成相反,这也就是 -n 的含义,所以 n & -n 结果就是提取最右侧的1
⑧干掉一个数n二进制位中最右侧的1
其实这道题就是把一个数的二进制位的最右边的1变成0,质押n & (n - 1) 即可,我们先来看下 n - 1 的含义:

所以n -1 的含义就是:将最右边的1的包括它在内右边位全部变成相反,所以n & (n - 1)的含义就是:

到这里,可以通过下面三道题来测试一下:(答案放在文章最后)
⑨异或(^)运算的运算律
- a ^ 0 = a
- a ^ a = 0
- a ^ b ^ c = a ^ (b ^ c)
通过异或运算,下面两道题就能很快解决:
260. 只出现一次的数字 III - 力扣(LeetCode)
部分OJ题详解
面试题 01.10. 判定字符是否唯一
面试题 01.01. 判定字符是否唯一 - 力扣(LeetCode)

我们来分析下这道题:
- 首先是暴力解法,直接用双指针,将每个单个字符一个一个去异或,如果结果为0,则代表有相同字符,返回false
- 解法二:就是用把每个字符扔哈希表里去,扔进去之前先判断下,如果value为0,就扔进去,如果为1,那么就返回false,时间和空间复杂度是O(N)
- 由于只有小写字母,我们其实只需要创建一个26大小的数组即可,用0或1表示是否出现即可。所以我们可以借助位图思想,因为这样可以把空间利用到极致。
- 优化:鸽巢原理,有26个英文字母,当目标字符串长度>26,必定有一个字符重复
class Solution {
public:
bool isUnique(string astr)
{
if(astr.size() > 26) return false;
int x = 0;
for(int i = 0; i < astr.size(); i++)
{
int a = astr[i] - 'a'; //得出字符在32位比特位的位置,(1 << a)相当于将该位置置1
if(x & (1 << a))
{
//走到这里说明就是该字符第二次出现,返回false
return false;
}
else
{
//走到这里,说明是第一次出现,将对应的比特位置1,与
x |= (1 << a);
}
}
return true;
}
};
268.丢失的数字

解释下题目:题目说给我们一个[0, n]中n个数的数组,按道理来说应该有n + 1个数的,所以有数字缺失,题目就是让我们想办法找出这个数,下面分析下题目:
- 解法一:先排序,然后和完整数组比较,如果值不相同,就是缺失的数
- 解法二:用数学办法,高斯求和公式,(首项 + 末项)*项数 / 2,求出总和,再减去数组中所有元素的和,最后得到的结果就是缺失的数字
- 解法三:异或运算。假设nums = [0, 1, 3, 5, 4],我们构建完整数组,[0, 1, 2, 3, 4, 5],然后把这两坨数全异或在一起,最终结果就是缺失的数字,因为异或的规则就是“相同为0”,那么不同的数就是缺失的数
解法一:
class Solution {
public:
int missingNumber(vector<int>& nums)
{
sort(nums.begin(), nums.end());
for(int i = 0; i < nums.size(); i++)
{
if(nums[i] != i)
return i;
}
return nums.size();
}
};
解法二:
class Solution {
public:
int missingNumber(vector<int>& nums)
{
int len = nums.size();
int sum = len * (len + 1) / 2;
for(auto e : nums) sum -= e;
return sum;
}
};
解法三:
class Solution {
public:
int missingNumber(vector<int>& nums)
{
int x = 0;
for(auto e : nums) x ^= e; //把数组中所有数搞进去
for(int i = 0; i <= nums.size(); i++) x ^= i; //异或完整数组
return x;
}
};
371.两整数之和

题目要求很简单,简直就是把位运算摆在你脸上了,下面我们来分析下这道题:
- 解法一:异或运算,因为异或运算是“无进位相加”,那么我们只需要想办法变成“进位相加即可”
关于这个我们举个例子:

class Solution {
public:
int getSum(int a, int b) {
while(b != 0)
{
int x = a ^ b; //先算出无进位相加结果
unsigned int carry = (unsigned int)(a & b) << 1; //算出进位
a = x; b = carry;
}
return a;
}
};
137.只出现一次的数字 Ⅱ
137. 只出现一次的数字 II - 力扣(LeetCode)

我们来分析下这道题:
所有的数都是int类型,所以所有的数都是32位的,根据题目,可以判断出上面所有的数的某一个比特位的和,可能会出现下面四种情况:

class Solution {
public:
int singleNumber(vector<int>& nums) {
int ret = 0;
for(int i = 0; i < 32; i++)
{
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;
}
};
注:如果每个数出现n次,就把最后 %3 的操作变成 %n 就可以了
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;
}
};
面试题 17.19. 消失的两个数字
面试题 17.19. 消失的两个数字 - 力扣(LeetCode)

我们来分析下这道题:
- 这道题其实是把“丢失的数字 + 只出现一次的数字 Ⅲ”,解法就是位运算
- 题目给我们的是nums,那么假设完整数组1 ~ N就是“原nums的值” + a + b,那么我们把nums和1~N搞在一起,那么就变成了:a和b出现了一次,其余的数字都出现了两次,这就和“只出现一次的数字Ⅲ了”:260. 只出现一次的数字 III - 力扣(LeetCode)
- 步骤一:将所有的数异或在一起,然后最终我们得到的结果就是tmp = a^b
- 步骤二:我们要想办法把这两个数分开,找到tmp中比特位为1的那一位,(因为a和b两个数是不一样的,所以a^b绝对不等于0,所以tmp中肯定有一个位是1),记这个位置为x为,既然tmp的x位为1,那么a和b肯定有一个的x位为0,有一个为1,反正不相同
- 步骤三:找到x位置后,根据x位的不同,划分成两类异或,一类异或结果就是a,另一类就是b
class Solution {
public:
vector<int> missingTwo(vector<int>& nums) {
//1,将所有的数异或在一起,异或两次
int tmp = 0;
for(auto e : nums) tmp ^= e;
for(int i = 0;i <= nums.size() + 2; i++) tmp ^= i;
//2,找出a和b中比特位不同的那一位,也就是tmp中位为1的位置
int diff = 0;
while(true)
{
if(((tmp >> diff) & 1) == 1) break;
else diff++;
}
//3,根据diff 位的不同,将所有的数据划分为两类来异或
int a = 0, b = 0;
for(int e : nums)
{
if(((e >> diff) & 1) == 1)
b ^= e;
else
a ^= e;
}
for(int i = 0;i <= nums.size() + 2; i++)
{
if(((i >> diff) & 1) == 1)
b ^= i;
else
a ^= i;
}
return {a,b};
}
};
最开始五道题的答案(按顺序):
class Solution {
public:
int hammingWeight(int n)
{
int ret = 0;
for(int i = 0;i < 32; i++)
{
if(n & (1 << i))
{
ret++;
}
}
return ret;
}
};
class Solution {
public:
vector<int> countBits(int n)
{
vector<int> v;
for(int i = 0;i <= n; i++)
{
int a = i, ret = 0;
while(a > 0)
{
a &= (a - 1); //该操作是将a的最右边的1变成0,执行该步骤的次数代表a有多少个1
ret++;
}
v.push_back(ret);
}
return v;
}
};
class Solution {
public:
int hammingDistance(int x, int y)
{
//异或操作,相同为0,不同为1
//只要把这两个数异或一下,其中1的数量就是这两个数不相同位的个数
//然后再统计异或后1的个数就可以得到汉明距离
int a = x ^ y;
int ret = 0;
while(a > 0)
{
a &= (a - 1);
ret++;
}
return ret;
}
};
class Solution {
public:
int singleNumber(vector<int>& nums)
{
int ret = 0;
for(int i = 0;i < nums.size(); i++)
{
//异或操作,相同为0,不同为1
//把数组每个数都相互异或,由于数组只有一个出现一次的数,那么其余重复的数异或之后就全变成0了
ret ^= nums[i];
}
return ret;
}
};
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
unordered_map<int,int> map;
for(auto e : nums)
{
map[e]++;
}
vector<int> v;
for(auto [x, y] : map)
{
if(y == 1)
{
v.push_back(x);
}
}
return v;
}
};
—— 位运算&spm=1001.2101.3001.5002&articleId=143182876&d=1&t=3&u=51b72d830d064337b381a6ca4cadbae3)
3841

被折叠的 条评论
为什么被折叠?



