位运算整理

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。

例题:剑指 Offer 15. 二进制中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;
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值