位运算在日常开发中不是很常用,但是巧妙的使用位运算可以大量减少运行开销,优化算法。因此在算法中为了减少开销,算法的复杂度有要求的情况下,位运算会起到意想不到的效果。

位运算

1.Java支持的位运算符

&:按位与。

|:按位或。

~:按位非。

^:按位异或。

<<:左位移运算符。

“>>”:右位移运算符。

<<<:无符号右移运算符。

1)按位与 &

Java 位操作 java位运算的运算规则_Java 位操作

规则总结:只有两个操作数对应位同为1时,结果为1,其余全为0. (或者是只要有一个操作数为0,结果就为0)。

2)按位或 |

Java 位操作 java位运算的运算规则_位运算_02

规则总结:只有两个操作数对应位同为0时,结果为0,其余全为1.(或者是只要有一个操作数为1,结果就为1)。

3)按位非 ~

Java 位操作 java位运算的运算规则_位运算_03

4)按位异或 ^

Java 位操作 java位运算的运算规则_算法_04

5)左位移 <<

符号位不变,低位补0。
2:10
2<<2:1000 =8
M << n = M * 2n

6)右位移 >>

低位溢出,符号位不变,并用符号位补溢出的高位。

如:-6>>2结果为-2。

Java 位操作 java位运算的运算规则_算法_05

这里总结一下,关于负数或者正数来说,移位的时候是一样的,但是在补位的时候,如果最高位是0就补0,如果最高位是1就补1
由此我们得出一个快速的算法 M >> n 其实可以这么算 M >> n = M / 2n

力扣关于位运算的题

1)剑指offer65:不用加减乘除做加法。

题目:写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。

解题思路:设两数字的二进制形式a,b,其求和s=a+b。

Java 位操作 java位运算的运算规则_操作数_06


无进位和n与进位c的计算公式(适用于正数和负数的加法)

Java 位操作 java位运算的运算规则_操作数_07


s=(非进位和n)+(进位c). s=a+b=>s=n+c 循环求n,c直到进位c=0,s=n (这样就不用加法了)

Java 位操作 java位运算的运算规则_java_08


算法代码

class Solution {
    public int add(int a, int b) {
        while(b!=0){ //进位等于0跳出
            int c =(a&b)<<1;  //进位
            a=a^b;  //非进位和
            b=c;  //进位b
        }
        return a;
    }
}
2)剑指offer56-Ⅰ:数组中数字出现的次数。

题目:一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

解题思路:异或运算有个重要的性质,两个相同数字异或为0,对任意整数a⊕a=0。

本题难点:数组nums有两个只出现一次的数字,无法通过异或直接得到

两个只出现一次的数字为x,y,由于x不等于y,则x和y二进制至少有一位不同

  1. 遍历nums执行异或
  2. 循环左位移计算m,找到x⊕y某为1的二进制位;初始化一个辅助变量m=1,通过与运算从右向左循环判断,可获取整数x⊕y首位1,记录于m中。
  3. 拆分nums为两个子数组
  4. 分别遍历两个子数组执行异或(通过遍历判断nums中各数字和m做与运算)
  5. 返回

算法代码:

class Solution {
    public int[] singleNumbers(int[] nums) {
        int x=0,y=0,n=0,m=1;
        for(int num : nums){
            n^=num;  //遍历nums执行异或
        }
        while((n&m)==0){ //循环左位移计算m
            m<<=1;
        }
        for(int num : nums){ //拆分nums为两个子数组
            if((num&m)!=0) x^=num; //当num&m!=0
            else y^=num; //当num&m==0
        }
        return new int[] {x,y};
    }
}