每日算法总结——位运算经典题目

题目一、位运算求较大值

给定两个有符号32位整数a和b,返回a和b中较大的。

【要求】不用做任何比较判断。

解题思路

  • 不能通过 <, > 比较,则我们可以计算 a - b 的值,然后判断结果的符号得知 a 和 b 哪个大
  • 判断一个数是正数还是负数,只需判断它的符号即可,对于int类型,它的符号位就是二进制首位,也就是说,将一个int类型的数右移31位,就可以在最低位得到它的符号。
  • 上边通过判断a - b的符号来判断a与b孰大孰小的方法是有问题的,会有a - b溢出的情况出现,但这种情况只存在于a和b符号不相同的情况,因此我们可以进一步判断:
    • 什么情况下a大于b?
      1. a与b符号相同,且a - b > 0
      2. a与b符号不同,且a > 0
    • 什么情况下a小于b?
      • a不大于b的情况下😏,其实就是上面情况的反面
public class GetMax {
    /**
     * 符号反转
     * 请保证参数n,不是1就是0
     */
    public static int flip(int n) {
        return n ^ 1;
    }

    /**
     * n是非负数,返回1
     * n是负数,返回0
     */
    public static int sign(int n) {
        return (n >> 31) & 1;
    }

    /**
     * 大小比较方法一,a - b 可能会溢出
     */
    public static int getMax1(int a, int b) {
        int c = a - b;
        // a - b 为非负,scA为1;a - b 为负,scA为0
        int scA = sign(c);
        // scA为0,scB为1;scA为1,scB为0
        int scB = flip(scA);
        // c > 0,返回a;c < 0,返回b
        return a * scA + b * scB;
    }

    /**
     * 大小比较方法二
     */
    public static int getMax2(int a, int b) {
        int c = a - b;
        int sa = sign(a);
        int sb = sign(b);
        int sc = sign(c);
        int difSab = sa ^ sb;
        int sameSab = flip(difSab);
        // 定义返回A的条件:1. a和b符号相同,c大于0;2. a和b符号不同,a大于0;
        int returnA = difSab * sc + sameSab * sa;
        int returnB = flip(returnA);
        return a * returnA + b * returnB;
    }
}
题目二、判断一个32位正数是不是2的幂、4的幂

解题思路

  • 首先要知道如果一个数是2的幂,那它应该有什么特点?二进制只有一个位上是1,其余都是0。
  • 如果一个数是4的幂,那它应该有什么特点?首先它一定是2的幂,所以它的二进制数也只有一个位上是1,其余都是0;其次,1的位置也是有讲究的,只能是隔位出现,即1可能出现的位置为01010101...0101
  • 如何判断一个数是2的幂:让该数减去1,就会得到一个类似 00...0011...11 的数,显然该数与原数相与&就会得到0。
  • 如何判断一个数是4的幂:首先判断它是否是2的幂,如果是,则接着判断,二进制的1是否出现在了正确的位置上,就将该数与10101010...1010相与,如果结果为0,则证明它是4的幂。
public class Power {
    public static boolean is2Power(int n) {
        return (n & (n - 1)) == 0;
    }

    public static boolean is4Power(int n) {
        // 0xaaaaaaaa -> ...0101010
        return (n & (n - 1)) == 0 && (n & 0xaaaaaaaa) == 0;
    }

    public static void main(String[] args) {
        System.out.println(is4Power(4));
    }
}
题目三、实现32位整数的加减乘除运算

题目描述:给定两个有符号32位整数a和b,不能使用算术运算符,分别实现a和b的加、减、乘、除运算
【要求】
如果给定a、b执行加减乘除的运算结果就会导致数据的溢出,那么你实现的函数不必对此负责,除此之外请保证计算过程不发生溢出。

解题思路

  • 加法:首先对于异或运算,a ^ b得到的是a和b无进位相加的结果(比如01101 ^ 00111 = 01010),而a & b得到的是两个数的进位信息(01101 & 00111 = 00101,等于1表示该位置上的数需要进位),而(a & b) >> 1a ^ b相加即为a + b的结果,所以又回到了两数相加的情况,就这样一直循环,直到进位信息为0,表示不需要进位了,此时返回异或后的结果即可。

    public static int add(int a, int b) {
        int sum = a;
        while (b != 0) {
            sum = a ^ b;
            b = (a & b) << 1;
            a = sum;
        }
        return sum;
    }
    
  • 减法:数a与数b相减,可以看成数a与数b的**补码**相加

    /**
     * 求补码
     */
    public static int complement(int n) {
        return add(~n, 1);
    }
    
    /**
     * 减法
     */
    public static int minus(int a, int b) {
        return add(a, complement(b));
    }
    
  • 乘法:有点类似计组里面的原码一位乘,假设a为被乘数,b为乘数,我们可以看成是b个a相加,但是是按照我们平常竖式运算的方式相加:

    • 根据b最低位是0还是1,来决定sum加0还是加a
    • 由于每次相加都是错位相加,所以在相加前,需要将a左移一位(不考虑溢出的情况)
    • 相加完,把b右移一位,以便取到下一位
    public static int multi(int a, int b) {
        int res = 0;
        while (b != 0) {
            if ((b & 1) != 0) {
                res = add(res, a);
            }
            a <<= 1;
            b >>>= 1;
        }
        return res;
    }
    
  • 除法:也是类似我们平常的竖式除的方法,假设a是被除数,b是除数,则我们首先需要进行一个对位的过程,也就是把b左移到恰好不超过a的位置:在这里插入图片描述

    通过对位,我们可以得出最高位的1在哪个位置,我们利用位运算也能实现这个过程

    • 首先我们需要计算出b左移多少位,才能恰好不超过a;由于b左移可能会出现溢出,所以我们通过让a右移来计算需要移动的位数n
    • 计算出需要移动的位数n后,就知道最高位的1,在哪个位置,左移n位之后加到res中
    • 然后需要把a减去b左移n位后的结果赋值给a,重复上述过程
// 判断是否为负数
public static boolean isNeg(int n) {
   return n < 0;
}

// 取相反数
public static int negNum(int n) {
   return -n;
}

// 除法运算
public static int div(int a, int b) {
   int x = isNeg(a) ? negNum(a) : a;
   int y = isNeg(b) ? negNum(b) : b;
   int res = 0;
   for (int i = 31; i > -1; i = minus(i, 1)) {
       if ((x >> i) >= y) {
           res |= (1 << i);
           x = minus(x, y << i);
       }
   }
   return isNeg(a) ^ isNeg(b) ? negNum(res) : res;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值