文章目录
1.判断最低位是否为1(n&1)
通过位运算的&运算符,他的效果是与n%2相同的
如:
5的二进制码为101,1的二进制码为001,则5&1=101&001=001,所以他的值为1
而5是奇数,5%2=1,所以他的结果也为1
4的二进制码为100,1的二进制码为001,则4&1=100&001=000,所以他的值为0
而4是偶数,4%2=0,所以他的结果也为0
2.左移与右移(>>、>>>、<<、<<<)
">>“表示有符号右移,若该数为正,则高位补0,而若该数为负数,则高位补1。
如:2>>1=1 -2>>1=-1
”>>>“表示无符号右移,也叫逻辑右移,若该数为正,则高位补0,而若该数为负数,则高位同样补0(即正数与”>>"运算符结果相同,负数与>>运算符结果不同)。
如:2>>1=1 -2>>2147483647
3.翻转最低位的1( n&(n-1) )
如:
26的二进制码为10110,25的二进制码为10101,则26&(25-1)=26&25=10100(二进制)=24
24的二进制码为10100,23的二进制码为10011,则24&(24-1)=24&23=10000(二进制)=16
16的二进制码为10000,15的二进制码为01111,则16&(16-1)=16&15=00000(二进制)=0
可以发现这个运算每进行一次,都会将二进制码的最低位的1进行翻转。所以我们可以用来求一个数的二进制有几位是1。
代码:
public class Solution {
public int hammingWeight(int n) {
int ret = 0;
while (n != 0) {
n &= n - 1;
ret++;
}
return ret;
}
}
4.获取最低位的1(n&-n)
如:
6&-6=00000110&11111010=00000010=2
5&-5=00000101&11111011=00000001=1
因此,n 和 −n 只有一个共同点:最右边的 1。这说明 x & (-x)
将保留最右边的 1。并将其他的位设置为 0。
5.判断正负与求绝对值(n>>>31&1)
“>>>”是无符号右移,最高位补0。右移31位即可获取该数字的最高位(符号位),若为正,则n>>>31&1的结果为0,若为负,则结果为1.
求绝对值:n*(1-(n>>>31&1)<<1)
根据上面的推断n>>>31&1的结果进行左移之后,负数的值为2,正数的值为0。所以,当n为负数时n*(1-2)就为正数,n为正数时n*(1-0)其值不变。
6.异或查找出现一次的数字(^)
异或(^)该位运算有以下几种结果:
- 若两数相等,异或结果为0
- 任何一个数,异或0的结果都为它本身
- 一个数,异或1的结果为他按位取反
- 若两数不相等,则异或后的结果的二进制码中,1为两者不相等的位,0为两者相等的位
例:
(1)若给你一组数,里面有一个数字只出现了一次,其余出现了两次,让你找出这个数。则将数组中数字全部异或一遍即可
输入:nums = [1,2,4,1,4,3,3]
输出:2
//将这个数组全部异或一次,最终的结果就是出现一次的数。
public int singleNumbers(int[] nums) {
int ans=0;
for(int i:nums){
ans=ans^i;
}
return ans;
}
(2)若给你一组数,里面有两个数字只出现了一次,其余出现了两次,让你找出这两个数。则可以使用分组异或。
输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]
public int[] singleNumbers(int[] nums) {
int ans=0;
//将全部数字异或一次,结果的二进制码中,所有的1便为那两个只出现一次数字不相等的位
for(int i:nums){
ans=ans^i;
}
int index=1;
//通过位移找到结果中最低位的1
while((ans&index)==0){
index<<=1;
}
int one=0,zero=0;
for(int i:nums){
//若该位为0,则分到zero组,若该位为1,则分到one组
if((i&index)==0)
zero=zero^i;
else
one=one^i;
}
//两个相等的数,必然会被分到同一组,因为其二进制码相同。那两个只出现一次的数,必然会被分到不同组,因为判断的index为我们找出的他们不相同的位
return new int[]{zero,one};
}
7.俄罗斯农民乘法(快速乘)
俄罗斯农民乘法经常被用于两数相乘取模的场景,如果两数相乘已经超过数据范围,但取模后不会超过,我们就可以利用这个方法来拆位取模计算贡献,保证每次运算都在数据范围内。
我们常常可以用n>>1来代表n除以2,以n<<1来代表n乘以2。
所以我们是否可以得到一个递推公式:
那么A 和 B两数相乘的时候我们如何利用加法和位运算来模拟?
将 B二进制展开,如果 B 的二进制表示下第 i 位为 1,那么这一位对最后结果的贡献就是 A∗(1<<i),即 A<<i。我们遍历 B 二进制展开下的每一位,将所有贡献累加起来就是最后的答案。
例:
3*2 = 11*10(二进制) = 3<<1 = 110(二进制) = 6
6*4 = 110*100(二进制) = 6<<2 = 11000(二进制) = 24
6*6 = 110*110(二进制) = (6<<1)+(6<<2) = 1100(二进制)+11000(二进制) = 12+24 = 36
16*15 = 10000*1111(二进制) = (16<<0)+(16<<1)+(16<<2)+(16<<3) = 10000+100000+1000000+1000000(二进制) = 16+32+64+128 =240
代码演示如下:
public int quickMulti(int A, int B) {
int ans = 0;
//如果B不为0,则一直右移判断最低位是否为1
for ( ; B!=0; B >>= 1) {
//如果此时最低位为1,则说明找到了B二进制中为1的位置
if ((B & 1)!=0) {
//将当前的A的值加入ans中
ans += A;
}
//继续将A左移,相当于给B除以2,给A乘以2
A <<= 1;
}
return ans;
}