Java基础之位运算

Java基础之位运算


前言

  • 位运算最大的好处是可以提高程序性能
  • 正数的反码还是等于原码,负数的反码就是他的原码除符号位外,按位取反
  • 正数的补码等于他的原码,负数的补码等于反码+1
  • 负数的二进制采用补码,比如-1 原码:1000 0001,反码:1111 1110,补码:1111 1111
  • Java 中 int 数据类型是32位、有符号的以二进制补码表示的整数

基础

1. 按位与 (&)

  • 规则:两个操作数对应位——同1为1,其余为0

  • 技巧1:等价于一个数对 2 ^ n 取模(求余数)—— 取模运算转化成位运算 (在不产生溢出的情况下)

    • a & 0 等价于 a % 1
    • a & 1 等价于 a % 2
    • a & 3 等价于 a % 4
    • a & 7 等价于 a % 8
    • a & (2 ^ n - 1) 等价于 a % (2 ^ n )
    • 示例
    public static void main(String[] args) {
    	int a=23;
    	int b=7; 
    	System.out.println(a & b);	
    }
    输出结果:7
    
  • 技巧2:判断一个数是偶数还是奇数

    • a & 1==0,a为偶数
    • a & 1==1,a为奇数
  • 技巧3:用于消去一个数转化为二进制数后的最后一个1

    • 示例
    x & (x-1)
    x = 1100
    x-1 = 1011
    x & (x-1) = 1000
    
    • 应用1:判断一个大于0的整数是不是2的幂
      • x & (x-1) == 0是2的幂,如果是2的幂,那一定只有1个1,消去这个1,变为0
      • 否则不是
    • 应用2:计算在一个 32 位的整数的二进制表示中有多少个 1
      • 由 x & (x-1) 消去x最后一位知。循环使用x & (x-1)消去最后一位1,计算总共消去了多少次即可
    • 应用3:将整数A转换为B,需要改变多少个bit位
      • 思考将整数A转换为B,如果A和B在第i(0<=i<32)个位上相等,则不需要改变这个BIT位,如果在第i位上不相等,则需要改变这个BIT位。所以问题转化为了A和B有多少个BIT位不相同。联想到位运算有一个异或操作,相同为0,相异为1,所以问题转变成了计算A异或B之后这个数中1的个数
  • 技巧4:取低位8bit,高位清0 x & 255

  • 技巧5:取x的奇数位并将偶数位用0填充 x & 0xAAAA

  • 技巧6:取x的偶数位并将奇数位用0填充 x & 0x5555

2. 按位或 (|)

  • 规则:有1为1,全0为0
  • 技巧:

3. 按位取反 (~)

  • 规则:~是一元操作符,将1变0,0变1

  • 技巧:一个数的相反数 = 对一个数进行取反加1

  • 示例

    public static void main(String[] args) {
    	int a=123;
    	System.out.println(~a+1);
    }
    输出:-123
    

4.按位异或 (^)

  • 规则:相同为0,否则为1

    • 异或的计算表
    操作数1 操作数2 按位异或
    0 0 0
    0 1 1
    1 0 1
    1 1 0
    • 规律:异或0具有保持的特点,而异或1具有翻转的特点
    • 异或运算的含义是二进制下不考虑进位的加法
    • 计算规律:
      • a ^ a =0
      • a ^ 0 =a
      • a ^ b =b ^ a
      • a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c
      • d = a ^ b ^ c 可以推出 a = d ^ b ^ c
      • a ^ b ^ a = b
  • 技巧1:不用temp交换2个整数

    public static void main(String[] args) {
    	int a=15;
    	int b=2;
    	a ^= b; //a = a ^ b;
        b ^= a; //b = b ^ a; --> b = b ^ (a ^ b) --> b = a
        a ^= b; //a = a ^ b; --> a = a ^ (b ^ a) --> a = b
    	System.out.println(a+"-"+b);
    }
    输出:2 - 15
    
  • 技巧2:翻转操作,初始值为1,操作一次变为0,再操作一次变为1: num = 1^num //num为原始值

  • 技巧3:给定一个数组,找出其中只出现一次的那个数

    • 示例1:Given [1,2,2,1,3,4,3], return 4
    • 数组中,只有一个数出现一次,剩下都出现两次
    • 解题思路:因为只有一个数恰好出现一个,剩下的都出现过两次,所以只要将所有的数异或一遍,就可以得到唯一的那个数
    • 代码
    public static void main(String[] args) {
    	int a[]={1,2,2,1,3,4,3};
        int ans=0;
        for(int i=0;i<7;i++){
            ans^=a[i];
        }
        System.out.println("ans="+ans);
    }
    输出:ans=4
    
    • 示例2:Given [1,1,2,3,3,3,2,2,4,1] return 4
    • 数组中,只有一个数出现一次,剩下都出现三次
    • 解题思路:自定义某种三进制运算符 #,使 0 # 1 = 1,1 # 1 = 2,2 # 1 = 0
     1 |0 0 0 1
     1 |0 0 0 1
     2 |0 0 1 0
     3 |0 0 1 1
     3 |0 0 1 1
     3 |0 0 1 1
     2 |0 0 1 0
     2 |0 0 1 0
     4 |0 1 0 0
     1 |0 0 0 1
     看最右边一列:1101110001,有6个1
     看中间一列:0011111100,有6个1
     看右数第三列:0000000010,有1个1
     我们只需要把1的个数是3的倍数的列写成0,不是3的倍数写成1
     最后写出:0100 (4)
     原因:如果所有数字都出现了 3 次,那么每一列的 1 的个数就一定是 3 的倍数。
     One+ = (One ^ B) & (~Two)
     Two+ = (~One+) & (Two ^ B)
    
    • 代码
    class Solution {
        public int singleNumber(int[] nums) {
            int one = 0, two = 0, temp = 0;
            for (int num : nums) {
                temp = (two & num) | (one & ~num);
                two = (~one & ~two & num) | (two & ~num);
                one = temp;
            }
            return two;
            // 这里return two的原因是第二个状态为 01,one 为 0,two 为 1
        }
    }
    
    • 参考链接:Leetcode 137 Single Number II(模拟三进制法,图表解析)
    • 示例3:Given [1,2,2,3,4,4,5,3] return 1 and 5
    • 数组中,只有两个数出现一次,剩下都出现两次,找出出现一次的
    • 解题思路:有了第一题的基本的思路,我们不妨假设出现一个的两个元素是x,y,那么最终所有的元素异或的结果就是res = x^y。并且res!=0,那么我们可以找出res二进制表示中的某一位是1,那么这一位1对于这两个数x,y只有一个数的该位置是1。对于原来的数组,我们可以根据这个位置是不是1就可以将数组分成两个部分。求出x,y其中一个,我们就能求出两个了
    • 代码
    
    

5.左移 (<<)

  • 规则:将一个数的各二进制位全部左移 n 位,右端低位补0
  • 示例1:9 << 2 = 36 ----> 0000 1001(9) ----> 0010 0100(36)
  • 示例2:-6 << 2 = -24 ----> 1111 1010 (-6) ----> 1110 1000 (-24)
  • 技巧1:乘法运算转化成位运算 (在不产生溢出的情况下) a * (2^n) 等价于 a<< n
    • java中int所能表示的最大数值是31位,加上符号位共32位
    • 任何数左移(右移)32的倍数位等于该数本身
    • 在位移运算m<<n的计算中,若n为正数,则实际移动的位数为n%32,若n为负数,则实际移动的位数为(32+n%32),右移,同理

6.右移 (>>)

  • 规则:将一个数的各二进制位右移N位,移到右端的低位被舍弃
    • 如果该数为正,则高位补0
    • 如果该数为负,则高位补1
  • 示例1:15 >> 2 = 3 ----> 0000 1111 (15) ----> 0000 0011 (3)
  • 示例2:-6 >> 2 = -2 ----> 1111 1010 (-6) ----> 1111 1110 (-2)
  • 技巧1:除法运算转化成位运算 (在不产生溢出的情况下) a / (2^n) 等价于 a>> n

7.无符号右移 (>>>)

  • 规则:低位溢出,高位补0。注意,无符号右移(>>>)中的符号位(最高位)也跟着变,无符号的意思是将符号位当作数字位看待
  • 示例:-1>>>1,结果为2147483647,即int类型所能表示的最大整数
  • 其他得到 Integer.MAX_VALUE 的方法
    • ~(1 << 31)
    • (1 << -1)-1
    • ~(1 << -1)

综合运用

  • 求两数的平均值

    public static void main(String[] args) {
    	int a=3;
    	int b=7;
    	System.out.println((a&b)+((a^b)>>1));
    }
    输出:5
    
  • 求一数的绝对值

    public static void main(String[] args) {
    	int a=-15;
    	int b=a>>31;
    	System.out.println((a^b)-b);
    }
    输出:15
    
  • 改变开关量的状态

    public static void main(String[] args) {
    	int x=1;
    	if (x==0) x=1;
    	else x=0;
    	System.out.println(x);
    }
    /***************等价于******************/
    public static void main(String[] args) {
    	int x=1;
    	x=0^1^x;
    	System.out.println(x);
    }
    
  • 对int型变量a转化为2进制数后的第k位进行操作

    • 取出第k位,即a>>k&1
    • 第k位清0,即a=a&~(1<<k)
    • 第k位置1,即a=a|(1<<k)
  • 对int型变量a循环左移k次或右移k次

    • 左移k次,即a=a<<k|a>>16-k
    • 右移k次,即a=a>>k|a<<16-k
  • 使用二进制进行子集枚举

    • 应用:给定一个含不同整数的集合,返回其所有的子集
    • 示例:如果 S = [1,2,3],有如下的解:[ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2] ]
    • 思路:使用一个正整数二进制表示的第i位是1还是0,代表集合的第i个数取或者不取
    • 所以从0到2n-1总共2n个整数,正好对应集合的2^n个子集
    S = {1,2,3}
    N bit Combination
    0 000 {}
    1 001 {1}
    2 010 {2}
    3 011 {1,2}
    4 100 {3}
    5 101 {1,3}
    6 110 {2,3}
    7 111 {1,2,3}
    
    

参考链接

发布了15 篇原创文章 · 获赞 4 · 访问量 729

猜你喜欢

转载自blog.csdn.net/weixin_38938338/article/details/103821760