算法通关村第十一关——位运算白银挑战笔记

上一篇内容中,我们总结了位运算常用的技巧,你记忆的如何了呢?我猜:你未必全部记住吧!那么,白银挑战笔记它来了,主要针对常用技巧进行实战巩固,进一步体会、理解,以至于使用位运算常用技巧来解决问题!纸上得来终觉浅,绝知此事要躬行!

1.“1”位移的妙用

在青铜挑战笔记中,我们对数字中1的内容做了三种总结,分别是:

获取第i位数据,判断是否为1【num & (1<

将最后一位数据1变成数据0【num&(num-1)】;

仅保留最后一位数据1,其余全部变成0【num&(-num)】。

就使用这三个总结的常用方法,完成“1”位移的妙用吧!

1.1 位1的个数

题目见LeetCode191,描述为统计int数字(二进制字符串)中数据位为1的个数。

题目分析,该怎么做?

方法一获取数字的每一位数据,如果该位数据为1,则1的个数增加!直接上代码!

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

方法二将该数字的最后一位1变成0,然后统计这样操作一共进行几次,进行几次,1的个数就为几个!直接上代码!

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

1.2 比特位计数

题目见LeetCode338,描述为给定一个整数n,返回由0到n的每个数字各包含几个1。

题目分析,题目位1的个数中,我们统计了一个数字中1的个数有几个,那么只需要遍历从0到n的所有数字,分别统计它们包含1的个数即可。厘清思路,直接上代码!

    //方法1,获取第i为数字    
    public static int[] myCountBits(int num) {
        int[] bits = new int[num + 1];
        for (int i = 0; i <= num; i++) {
            int count = 0;
            for (int j = 0; j < 32; j++) {
                bits[i] += (i >> j) & 1;
            }
        }
        return bits;
    }

    //方法2,将数字的最后数据1变成数据0
    public static int[] myCountBits2(int num) {
        int[] bits = new int[num+1];
        for (int i = 0; i <= num; i++) {
            bits[i] = countNumOne(i);
        }
        return bits;
    }

    public static int countNumOne(int num){
        int count = 0;
        while(num != 0){
            num &= num -1;
            count++;
        }
        return count;
    }

1.3 颠倒无符号的整数

题目见LeetCode190,描述为颠倒32位无符号整数!

如何颠倒数字?答:逆向过程,栈!那,能否使用位运算?答:可以!

逻辑如下:获取数字第i位数据(模板!!),反转后原数据第i位数据将变成新数据的第(31-i)位数据!

厘清思路,直接上代码!

    public static int myReverseBits(int n) {
        int res = 0;
        for (int i = 0; i < 32; i++) {
            res += ((n >>> i) & 1) << (31 - i);
        }
        return res;
    }

那,还有其他方法嘛?这个方法的时间复杂度是O(n)!

答:有!使用二分位反转,即可!

什么是二分位反转?首先将32位数据分割成前16位和后16位,将这两段数据进行翻转。然后将前后16位的数据分割成前8位和后8位,对这两段数据进行反转。依次执行这个过程,直到将2位数据分成前1位和后1位,对这两段数据进行反转。结合代码,理解这个二分位反转的过程,开阔眼界!

    public static int reverseBits2(int n) {
        //第一次翻转
        n = (n >>> 16) | (n << 16);
        //第二次翻转
        n = ((n & 0xff00ff00) >>> 8) | ((n & 0x00ff00ff) << 8);
        //第三次翻转
        n = ((n & 0xf0f0f0f0) >>> 4) | ((n & 0x0f0f0f0f) << 4);
        //第四次翻转
        n = ((n & 0xcccccccc) >>> 2) | ((n & 0x33333333) << 2);
        //第五次翻转
        n = ((n & 0xaaaaaaaa) >>> 1) | ((n & 0x55555555) << 1);
        return n;
    }

2.位运算实现加减乘除

2.1 位运算实现加法

从简单的加法特殊情况出发,比如01 1010 + 00 0010,如何进行加法运算?首先是二者相加的保留位01 1000,其次是二者相加的进位位00 0100最终结果 01 1000 + 00 0100 = 01 1100(注意:在这个过程中,仍可能出现保留位进位位相加产生新的进位位

那么,何时达到最终结果呢?答:没有进位位时,即进位位=0时,加法结束!

OK,加法的过程重点在于求得进位位保留位,怎么实现?

答:保留位:两个数字为1的数据变成0,为0的数据还是0,一个数据为0另一个数据为1则变成数据1,什么运算?异或!

进位位:两个数字为1的数据结果仍然是1不过向左移位1位,什么运算?与并且左移1位!

厘清思路,直接上代码!

    public static int plusByBit(int a, int b){
        int sign = 0;
        while(b != 0){
            //进位
            sign = (a & b) << 1;
            //保留位
            a ^= b;
            b = sign;
        }
        return a;
    }

2.2 位运算实现乘法

从简单的加法特殊情况出发,比如4*3,也就是4*(0011) = 4*(0010)+4*(0001),那么,就可以从比较小的乘数出发,取出数据为1的数据,结合被乘数的倍数,完成累加计算!

什么是被乘数的倍数?简单来说,被乘数是4,乘数3决定了其在1倍和2倍的时候应该累加计算,即4*(0010+0001)=4*(2+1)!

厘清思路,直接上代码!(结合代码,更易厘清思路!)

    public static int multiplyByBit(int a, int b){
        int min = Math.min(a, b);
        int max = Math.max(a, b);
        int ans = 0;
        while(min > 0){
            if((min & 1) == 1){
                ans += max;
            }
            min >>= 1;
            max += max;
        }
        return ans;
    }

OK,《算法通关村第十一关——位运算白银挑战笔记》结束,喜欢的朋友三联加关注!关注鱼市带给你不一样的算法小感悟!(幻听)

再次,感谢鱼骨头教官的学习路线!鱼皮的宣传!小y的陪伴!ok,拜拜,第十一关第三幕见!

你可能感兴趣的:(笔记)