位运算 in Java

180 阅读5分钟

小时候不懂事,不知道算法的重要性,在此明志苦练算法

位运算:

例1:

将int转化成二进制 常规思路: 再算二进制转十进制时,我们将二进制上每一位乘以其权重再相加,反过来计算的思路就是:用十进制数尝试投影到二进制的高位,投影成功后再将十进制数剩下的部分投影到次高位。

代码如下:

public void toBin2(int target){
        //最常规思路:由二进制转十进制的思路反转——将数投影在二进制的各个位置
        for (int i = 31; i >= 0 ; i--) {
            int curr = (int) ( target / Math.pow(2, i));
            System.out.print(curr);
            target = (int) (target % Math.pow(2,i));
        }
    }

思路给人的感觉就像这个: 在这里插入图片描述一层层滤出高位的值。

思路2: 内含位移运算的思想: 当我们将数右移一位的时候,是将其值/2,如果有余数表示原本最右边的值为1,没有余数则表示最右边的值为0。 所以我们每次将十进制数num除以2,如果有余数则记录下为1,没有则记录为0 代码:

public void toBin(int target) {
        //思路是:用target除以2,余数表示此位的数,再循环用商除以2。
        if(target <= 1){
            System.out.println(target);
            return;
        }
        System.out.print(target % 2);
        toBin(target / 2);
    }

思路3: 纯粹的位运算思想: 十进制数对应的二进制数,通过某些位运算可以暴露出某个位上是1还是0 例如a = 0010101,将 a & 0000001 是否等于0,可以判断a的最右边一位是否为0 代码:

public void toBin3(int target) {
        for (int i = 31; i >= 0; i--) {
            System.out.print((target & (1 << i)) == 0 ? 0 : 1);
        }
    }

例2:

熟练使用异或

基于异或最直观的性质:相同为0,不同为1,有几条更加便于使用的性质:

  1. 0 ^ N = N
  2. N ^ N = 0
  3. 可以将异或操作理解为:无进位相加
  4. 异或操作具有交换律
  5. 异或操作具有结合律

怎么理解以上操作? 在这里插入图片描述 无进位相加是指:二进制的各个位置上依次相加,但不进行进位。 按照无进位相加进行理解,进行异或其实可以看作各个位置上对有多少个1进行计数,有奇数个1则此为为1,反之成立。 这样就可以理解交换律结合律,以及0异或N等于N,N异或N等于0;

例1:

一个int数组中,只有一个数出现了奇数次,其他都出现偶数次,找出出现奇数次的数 {1,3,2,1,1,41,41,2,244,3,244}

常规思维: 遍历一遍,每个数和其出现次数加入哈希表,最后再遍历一遍哈希表 时间复杂度为O(N),空间复杂度也是O(N),但是用位运算可以将空间复杂度降低

位运算思路: 如果我将所有数全都异或一遍,出现偶数次的数会抵消,出现奇数次的数会保留 代码:

public int getSingle2(int[] ints) {
        int eor = 0;
        for (int num : ints
        ) {
            eor = eor ^ num;
        }
        return eor;
    }

说实话,优雅是优雅,但是很难将次方法进行拓展哎,只能怪咱功夫还不到家

例2:

一个int数组中,只有两个数出现了奇数次,其他都出现偶数次,找出出现奇数次的数 {1,3,2,1,1,41,41,2,3,244,3,244}

假如还要从位运算的角度突破,想想我们能得到什么 假设出现奇数次的数是a,b 依照异或运算的性质将所有数异或一遍,自然可以得到a ^ b 之后就是左神的神奇思路: a和b既然是不同的数,那么a和b肯定存在某一位不同,按照此位将数组中所有数分为两份,一份是此位是0,一份是此位是1(a,b自然分置在两份中),很容易想到两两成对的数都在一份中。 用a ^ b去异或其中一份中所有数,异或其中出现两次的数没有变化,而异或其中的a或者b会将a^b 消掉一个,得到a或者b 代码:

public int[] getSingle3(int[] ints) {
        int eor = 0;
        for (int num : ints
        ) {
            eor = eor ^ num;
        }
        int ab = eor; //a ^ b
        int i = 0; //用最右侧的1位上,这些数是0还是1来区分所有数
        for (; i < 32; i++) { //这一步是得到ab最右侧的1所在位置
            if ((ab ^ (int)(Math.pow(2, i))) < ab) {
                break;
            }
        }
        int ans = (int) Math.pow(2,i);
        int temp = 0;
        for (int num : ints
        ) {
            if ((num ^ ans) < num) {  //判断在这一位上是不是1,如果是1,异或1后会减少
                temp = temp ^ num;
            }
        }
        eor = eor ^ temp;
        int another = eor ^ ab;
        return new int[]{another, eor};
    }
  • 另外得到一个数最右边的1的办法还有:
    int index = eor & (~eor+1)
    
    原理不明,优就完事了
  • 另外怎么判断某个位置上是0还是1呢?这在位运算中有很多办法 假设要根据最后一位区分01001和01000 1. 用00001位与,如果结果是0,则表示原数最后一位是0 2. 用11110位或,如果结果比11110大,则表示原数最后一位是1 3. 用00001异或,其他位置不变,最后一位原本是1变为0,原本是0变为1,所以如果数和原本相比减少,则最后一位是1

待续......