今日为大家详细讲解一番关于常见位运算的操作,本文主要介绍一些位运算的操作符,然后再通过简单->中等->困难的例题,让大家彻底搞懂关于位运算的知识!
位运算的介绍!
1.基础位运算
">>"右移操作符!
"<<"左移操作符!
"~" 按位取反操作符!
"&"按位与操作符!
"|" 按位或操作符!
"^"异或操作符!
上面是一些常见的位运算操作符!下面来一一介绍每个运算符的作用!
1.“>>”右移操作符!
右移操作符分为两种,一种为逻辑移位,一种是算术移位! 二者的移位规则如下:
1. 逻辑移位:左边用0填充,右边丢弃
2. 算术移位:左边用原该值的符号位填充,右边丢弃
2."<<"左移操作符!
移位规则:左边抛弃,右边补0。
注:其中左移操作和右移操作还有另外一个中作用,左移的另一作用就是将原来的数字扩大两倍!右移操作将原来的数字缩小两倍!
3.按位取反
将原二进制数字的二进制位进行按位取反!包括符号位也要进行取反!
4.按位与
只有两个数字对应的比特位都为1,结果才为1!
注:有0则0!
5.按位或
两个数字对应的比特位存在1,结果就为1!
注:有1则1!
6.按位异或(也叫无进位相加)后面例题会根据此特性求解!
只有两个数字的二进制位不同的时候才为1!相同为0!
注:相同为0,相异为1!
介绍了上面的简单的概念之后,下面进入关于这些操作符的扩充了解!
位操作符的相关运算扩充!
1.确定一个数的二进制位为0或为1!
给定一个数字,确定其二进制位中第x为是0还是1!
其中要想判断第x位是0还是1!只需要将N这个数字,向右移动x位!使得该位移动到最低位!
再根据按位与操作即可判断改为是0还是1!
2.将第x位修改为1!
要想将第x位修改为1! 只需要先将1向左移动x位,让其与原数字的第x位对齐!然后进行或操作!即可将第x为修改为1! 图示如下:
n|=(1<<x);
3.将一个数字n的第x为修改为0!
原理类似于将第x为修改为1!只需要先将1向左移动x位!然后进行取反!因为不能将其他位进行修改,所以此时1的第x为0,其余位都为1,然后再进行按位与操作即可修改成功!
此时,只需要再次进行按位与操作即可将n的第x为修改为0!
n&=(~(1<<x));
3.位图的思想
位图的思想类似于哈希表!因为一个整形4byte,32个比特位!所以可以根据比特位来进行某些题目的求解!一会通过一些例题来体现位图的思想!
4.提取一个数最右边的1!
先求出此数的相反数!然后进行按位与操作即可!
求一个数的相反数的二进制时,可以发现一个规律,找到其最右侧的1,其左边区域都相反,右边区域不变!
可以看出,一个数的相反数,其二进制位中的数字,在第一次出现1的位置时,分为了两部分,左边全部与之前相反,右边不变!
所以根据此特性,再进行按位与操作即可以获得最右边的一个1!
n&(-n)!
5.干掉最右侧的1!
其实本质上就是进行了借位操作!因为借位都是先从低位进行操作!所以进行借位之后即可干掉最右侧的1!
n-1之后,其一定会减少一个1!然后进行按位与操作即可将最后一个1干掉!根据此特性还可以快速求出二进制数中1的个数!
后面会通过例题来体现此特征!
位运算常见的经典例题!
1.只出现一次的数字
136. 只出现一次的数字https://leetcode.cn/problems/single-number/
代码如下:
class Solution
{
public:
int singleNumber(vector<int>& nums)
{
//位操作!
int ret=0;
for(int i=0;i<nums.size();i++)
{
ret^=nums[i];
}
return ret;
}
}
本题直接通过异或操作直接秒杀!异或操作相同为0相异为1,因为只有一个数字出现一次而其他数字都出现两次,所以可以直接通过异或解决此题!
2.只出现一次的数字||
LCR 004. 只出现一次的数字 IIhttps://leetcode.cn/problems/WGki4K/
代码如下:
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)
{
if(((x >> i) & 1) == 1)
{
sum++;
}
sum %= 3;
} // 计算nums中所有的数的第 i 位的和
if(sum == 1) ret |= 1 << i;
}
return ret;
}
};
此题因为数组中只有一个元素出现1此,其余都出现了3次,故不能通过简单的异或进行求解!
那么该如何求解呢?我们可以通过每个数字相应的比特位之和来求出结果!图示如下:
可以看出,我们只需要将每一个比特位的和求出来然后再%3即可判断出出现一次的数字! 因为由上图我们可以看到,出现一次的数字和%3之后的结果相同,所以我们通过此特性可以巧妙求出出现一次的数字的各个比特位,依次求出比特位,最后即可得到只出现一次的数字!
3.只出现一次的数字|||
260. 只出现一次的数字 IIIhttps://leetcode.cn/problems/single-number-iii/
代码如下:
class Solution {
public:
vector<int> singleNumber(vector<int>& nums)
{
int i=0;
int ret=0;
for(;i<nums.size();i++)
{
ret^=nums[i];
}
//得到a^b的结果!!
//然后求出a^b的第一个一的位数!
int pos=0;
for(i=0;i<32;i++)
{
if(((ret>>i)&1)==1)
{
pos=i;
break;
}
}
vector<int>r(2);
for(i=0;i<nums.size();i++)
{
if(nums[i]>>pos&1==1)
{
r[0]^=nums[i];
}
else
{
r[1]^=nums[i];
}
}
return r;
}
};
通过此题,我们可以看出有两个出现一次的数字!所以我们将所以元素异或之后得到的结果是只出现一次的数字a,b的异或结果!那么如何将a,b分离出来呢?
我们知道!a^b一定有不相同的比特位! 那么我们就可以找到第一次出现1的比特位(div)!然后根据此特性,将a,b分离开来,由此再次遍历一遍数组,将数组分为两大阵营!以a为首和以b为首的两大阵营!根据与div按位与不同的结果就此分开,因为其余数组都出现两次,所以划分阵营,相同的数字肯定在同一阵营,在同一阵营,异或之后结果为0,最后即可求出a,b!
4.判断字符是否唯一
面试题 01.01. 判定字符是否唯一https://leetcode.cn/problems/is-unique-lcci/
代码如下:
class Solution {
public:
bool isUnique(string astr)
{
//根据鸽巢原理直接排除字符个数大于26的情况!
int len=astr.size();
if(len>26)
{
return false;
}
//采用位图的思想直接进行求解!每个比特位对应相应的字母!映射到int类型中!
//如果进入前个数为1的话,说明一定重复!直接返回false即可!否则一直遍历,直至字符串遍历完!!
int ret=0;
for(int i=0;i<len;i++)
{
int ch=astr[i]-'a';
if(((ret>>ch)&1)==1)
{
return false;
}
ret|=1<<ch;
}
return true;
}
};
算法思路:
此题我们可以利用哈希表进行统计数组出现的次数!但是我们可以根据位图的思想使空间复杂度降为o(1),定义一个整形变量,32个比特位,足够我们统计进行映射!每次进行统计!若统计前为0,则将此次统计的数字的比特位修改为1,根据按位或的特征将此比特位修改为1!若进入前为1,那么一定会重复!直接返回即可!否则一直判断,直至string遍历完毕!
注:还可以根据鸽巢原理(鸽巢原理又名狄利克雷抽屉原理、鸽笼原理。 10只鸽子放进9个鸽笼,那么一定有一个鸽笼放进了至少两只鸽子。)进行优化!
5.两整数之和
371. 两整数之和https://leetcode.cn/problems/sum-of-two-integers/
代码:
class Solution {
public:
int getSum(int a, int b)
{
//根据异或的无进位相加特性进行求解!
//再根据按位与的操作求出合适的进位处!!
int sum=0;
int ad=0;
while(b)
{
sum=a^b;
ad=(a&b)<<1;
a=sum;
b=ad;
}
return a;
}
};
思路:
题目明确指出不能使用‘+’,‘-’,这就大大限制了我们,但是我们前面介绍到了异或操作符!前面又说道异或操作符为无进位相加!可以根据此特性来进行求解!
又因为我们相加的结果没有进位!所以那么如何该得到进位呢? 我们知道只有两个数字对应的比特位都为1才能有进位的存在!而且进位进的是其前面那一位! 所以我们将a,b进行按位与操作!然后向左移一位,获得进位!然后与异或结果继续异或!直至进位为0,结束运算得到最后结果!
5.消失的两个数字
面试题 17.19. 消失的两个数字https://leetcode.cn/problems/missing-two-lcci/
代码:
class Solution {
public:
vector<int> missingTwo(vector<int>& nums)
{
//首先根据缺失的数字可以求出缺失两数字异或的结果!
//在根据消失的数字||思路即可求解!
int ret=0;
//得出缺失两数字的异或和!
for(auto ch:nums)
{
ret^=ch;
}
for(int i=1;i<=nums.size()+2;i++)
{
ret^=i;
}
//找出异或结果的第一个1的位置!
int div=0;
div=ret&(-ret);
//其中div就是第一次出现的1的位置! 该div直接表示为那个数字!
//再根据异或的性质,将数组中的元素分为两大阵营,然后每次异或即可得到缺失的两个数字!
vector<int>v(2);
for(auto ch:nums)
{
if((ch&div)==div)
{
v[0]^=ch;
}
else
{
v[1]^=ch;
}
}
for(int i=1;i<=nums.size()+2;i++)
{
//对应的比特位相同!
if((i&div)==div)
{
v[0]^=i;
}
else
{
v[1]^=i;
}
}
return v;
}
};
思路:
因为题目是从1~n缺少两个数字,我们可以容易的求出缺少两数字异或的结果!然后再根据只出现一次的数字|||本题的思路即可求解!
此题就是只出现一次的数字|||的思路加上一个两数异或的结果的结合!虽然难度为困难,但是实际上手还是很容易的!本题思路不再累赘,参考3即可!
至此,关于运算符运算的知识讲解结束,希望读完本篇文章,可以让读者对运算符的运算有了更深一步的认识,希望读者读完文章可以自行尝试一下上面的例题!对我们的位运算的性质掌握也会有很大的提升!
本文分享完毕,欢迎读者们多多评论,若有所收获,留下免费的小心心哦!