算法通关村——位运算

1. 常见的位运算

1.1 与 &

&:两个数对应的位都是1,那么结果才是1
1 & 1 = 1
1 & 0 = 0;
0 & 0 = 0;

1.2 或 |

|: 只要两个数对应的位有一个1,结果就是1
1 | 1 = 1;
1 | 0 = 1;
0 | 0 = 0;

1.3 异或^

^: 只有两个数的位都一样的时候才是返回0,否则返回1
1 ^ 1 = 0
0 ^ 1 = 1
0 ^ 0 = 0;

1.4 取反 ~

~: 就是取反,如果位是1,取反就是0
~1=0
~0=1

2. 移位运算

左移:<< 全部二进制左移若干位(如果左移一位,就是除以2)
右移:>> 全部二进制右移若干位

3. 位1的个数

位1的个数
编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。

3.1 右移

只需要将每一位和1进行与&操作,如果当前位是1,那么就计数
i=0 00000000000000000000000000001011 & …1=1
i=1 00000000000000000000000000000101 & …1=1
i=2 00000000000000000000000000000010 & …1=0
i=3 00000000000000000000000000000001 & …1=1

结果是3

 public int hammingWeight(int n) {
    
    
        int count =0;
        for(int i=0;i<32;i++){
    
    
            count+=(n>>i)&1;
        }
        return count;
    }

3.2 左移

先设置一个mask之用于左移比较是否位相同
00000000000000000000000000001011 & 1 =0
00000000000000000000000000001011 & 01 = 0
00000000000000000000000000001011 & 001 =0

00000000000000000000000000001011 & 00000000000000000000000000001 = 1
00000000000000000000000000001011 & 000000000000000000000000000001 = 0
00000000000000000000000000001011 & 0000000000000000000000000000001 = 1
00000000000000000000000000001011 & 00000000000000000000000000000001 = 1

 public int hammingWeight(int n) {
    
    
       int count =0;
       int mask = 1;
       for(int i=0;i<32;i++){
    
    
           if((n & mask) !=0){
    
    
               count++;
           }
           mask<<=1;
       }
       return count;
    }

3.3 n&(n-1)

将二进制最后一个1变成0,直到n最后变成0
00000000000000000000000000001011 & 00000000000000000000000000001010 = 00000000000000000000000000001010
count = 1;

00000000000000000000000000001010 & 00000000000000000000000000001001 = 00000000000000000000000000001000
count = 2;

00000000000000000000000000001000 & 00000000000000000000000000000111 =
0000000000000000000000000000000
count =3;

 public int hammingWeight(int n) {
    
    
        int count =0;
       while(n!=0){
    
    
           n=n&(n-1);
           count++;
       }
       return count;
    }

这种效率也是比较高的,经常使用,前两个位移都是需要遍历32次,而n&(n-1)只需要n为0的时候就退出
在这里插入图片描述

4. 比特位计数

比特位计数
给你一个整数 n ,对于 0 <= i <= n 中的每个 i ,计算其二进制表示中 1 的个数 ,返回一个长度为 n + 1 的数组 ans 作为答案。
输入:n = 2
输出:[0,1,1]
解释:
0 --> 0
1 --> 1
2 --> 10

4.1 右移

这里面的的计算1的个数就可以采用之前的( n>>i ) &1,其中n是当前的值,而i是右移的位数
很显然,因为元素大小是在0-n这个范围内,所以是按照相应下标就可以知道当前的值,那么再次&1比较,然后右移,就可以算出1的个数,然后赋值给数组。

    public int[] countBits(int n) {
    
    
        int [] res = new int [n+1];
        for(int i=0;i<=n;i++){
    
    
            for(int j=0;j<32;j++){
    
    
                res[i] += (i >>j)&1;
            }
        }
        return res;
    }

此方法缺点在于时间耗费比较长,因为每个元素都要遍历右移32次,才能计算1的个数

4.2 位的性质 n&(n-1)

public int[] countBits(int n) {
    
    
        int count =0;
        int [] res = new int [n+1];
        for(int i=0;i<=n;i++){
    
    
            res[i] = getOneCount(i);
        }
        return res;
    }

    public int getOneCount(int x){
    
    
        int count =0;
        while(x!=0){
    
    
            x=x&(x-1);
            count++;
        }
        return count;
    }

5. 颠倒二进制位

颠倒给定的 32 位无符号整数的二进制位。

提示:

请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在 示例 2 中,输入表示有符号整数 -3,输出表示有符号整数 -1073741825

5.1 逐位颠倒

一开始想的是直接反转这个二进制数,直接使用Integer的reverse是肯定不行的。
首先先获取到n的最后一位,然后n进行逻辑右移,和reversed反转数进行相加,直到n变成0
00000010100101000001111010011100右移31位,最低位是0
n逻辑右移1位
00000001010010100000111101001110右移30位,最低位是0

 public int reverseBits(int n) {
    
    
        int reversed =0;
        int length = 31;
        while(n!=0){
    
    
            reversed += (n&1)<<length;
            // 逻辑右移
            n >>>= 1;
            length--;
        }
        return reversed;
    }

6. 两整数之和

两整数之和
给你两个整数 a 和 b ,不使用 运算符 + 和 - ​​​​​​​,计算并返回两整数之和。
输入:a = 2, b = 3
输出:5

6.1 异或+与

2:010
3:011
5:101
可以看出相同为0,不同为1,这是异或,对于不需要进位的部分采用a^b , 但是需要关注首位是0还是1,进位部分就要采用a&b,只有两个都是1的时候才需要进位。

public int getSum(int a, int b) {
    
    
        while(b!=0){
    
    
            int sign = (a & b) << 1;
            a = a ^ b;
            b = sign;
        }
        return a;
    }

a&b= 010 010 << 1 = 100 sign = 100
a^b= 001 a = 001
b = 100

a&b = 100 & 001 = 000 000<1 =000 sign =0
a^b = 100 ^ 001 = 101 a = 101
b =0
退出

a=101 =5

7. 递归乘法

递归乘法
递归乘法。 写一个递归函数,不使用 * 运算符, 实现两个正整数的相乘。可以使用加号、减号、位移,但要吝啬一些。
输入:A = 3, B = 4
输出:12

7.1 位运算

例如3 * 4,就是3 * 2 ^ 2,也就是3<<2,左移两位
例如1 * 10,就是1 (2+8)就是1 * 2^1+12 ^3 就是1 <<1 +1<<3

先找出两者之间的最小值和最大值,选取最小值作为乘数的原因是次数较少,然后判断min的最右一位是不是1,是1就添加该位到结果中,然后最小值右移一位

 public int multiply(int A, int B) {
    
    
       int add = 0;
       int min = Math.min(A,B);
       int max = Math.max(A,B);
       for(int i=0;min!=0;i++){
    
    
           if((min & 1) == 1){
    
    
               add  += max<< i;
           }
           min >>= 1;
       }
       return add;
    }

例如 1 * 10 min = 1,max = 10, min &1 =1, add = 10 << 0 = 1010=10
min >>1 = 0 退出

例如 3* 4 min = 3 max =4 min & 1= 1 add = 4 << 0 =4
min >> 1 = 01 min & 1= 1 add = 4 + 4 << 1 = 4 + 8 = 12

猜你喜欢

转载自blog.csdn.net/qq_52843958/article/details/132343264