剑指 Offer II 001. 整数除法

题目

链接:https://leetcode.cn/problems/xoh6Oh

给定两个整数 a 和 b ,求它们的除法的商 a/b ,要求不得使用乘号 ‘*’、除号 ‘/’ 以及求余符号 ‘%’ 。

img

思路

第一反应是,这题是找茬吧!

对于除法,自己能一下子想到的,就是两种思路。第一种是用减来代替除。第二个就是位运算。

当然,对于一些边界值,还是需要做一下特殊处理。这样既可以提高效率,也可以防止错误。

  1. 被除数是0
  2. 除数是1
  3. 如题中所说,32位整数,也就是int类型,要注意溢出的边界。

解法一:暴力解法

所谓除法,就是看被除数里有几个除数。那用被除数把除数都减掉就好了。

当然,这里需要统一把两个数都转换成正数,方便处理。计算完结果,再判断是否要加上正负号即可。

逻辑

  1. 处理边界值
  2. 判断除数和被除数是否有负数,取绝对值后,记录结果是否需要加负号
  3. 循环的用被除数减去除数
  4. 返回最终的结果

代码

public int divide(int a, int b) {
    // 1.处理边界值
    if (a == 0 || b == 1) {
        return a;
    }

    // 溢出情况,即-2的31次方/-1,要将2的31次方处理成2的31次方-1
    if (b == -1) {
        return a == Integer.MIN_VALUE ? Integer.MAX_VALUE : -a;
    }

    // 2.判断结果是否为负数。只有a和b符号相同,结果才为正数。这里使用了^异或位运算符
    boolean isNegativeResult = a > 0 ^ b > 0;
    a = Math.abs(a);
    b = Math.abs(b);

    // 3.循环,用被除数减去除数,直到结果小于零
    int result = 0;
    while (a - b >= 0) {
        result++;
        a -= b;
    }

    // 4.根据前面的正负号返回结果
    return isNegativeResult ? -result : result;
}

解法二:位运算

相比于循环做减法的暴力方式,位运算的效率要高很多。但是要注意的一点是,左移或右移只能对一个数做2的n次方的乘或者除。所以我们要不断的细化结果。

比如,17/3,对3右移2位是12,右移3位是24。17介于12和24之间,因此你要先记录,17里包含至少4个3(2的2次方),之后再看5(17-12)里面有几个3。依次这么计算下去。

逻辑

其实我们直接用位运算的逻辑,替换掉暴力运算中,循环做减法的那部分逻辑即可。

代码

public int divide(int a, int b) {
    // 1.处理边界值
    if (a == 0 || b == 1) {
        return a;
    }

    // 溢出情况,即-2的31次方/-1,要将2的31次方处理成2的31次方-1
    if (b == -1) {
        return a == Integer.MIN_VALUE ? Integer.MAX_VALUE : -a;
    }

    // 2.判断结果是否为负数。只有a和b符号相同,结果才为正数。这里使用了^异或位运算符
    boolean isNegativeResult = a > 0 ^ b > 0;
    a = Math.abs(a);
    b = Math.abs(b);

    // 3.位运算逻辑
    int result = 0;
    // 因为最大是32位的数,因此我们从31开始
    for (int i = 31; i >= 0; i--) {
        // 因为对b左移会出现溢出的问题,因此我们对a做右移。
        // 又因为-2147483648在上一层的处理中,取绝对值后依旧是-2147483648,所以这里用无符号右移
        // 用(a >>> i) - b >= 0 而不是(a >>> i) >= b 也是为了防止溢出。
        if ((a >>> i) - b >= 0) {
            a = a - (b << i);
            result += 1 << i;
        }
    }

    // 4.根据前面的正负号返回结果
    return isNegativeResult ? -result : result;
}

无关紧要的进一步优化

这里我发现一个问题,即两种方案中的第二步,判断正负号以及取绝对值的逻辑,会影响1ms的性能。

即原有逻辑:

    // 2.判断结果是否为负数。只有a和b符号相同,结果才为正数。这里使用了^异或位运算符
    boolean isNegativeResult = a > 0 ^ b > 0;
    a = Math.abs(a);
    b = Math.abs(b);

改为下面的丑陋的写法:

    boolean isNegativeResult = false;
    if (a > 0 && b < 0) {
        b = -b;
        isNegativeResult = true;
    } else if (a < 0 && b < 0) {
        a = -a;
        b = -b;
    } else if (a < 0 && b > 0) {
        a = -a;
        isNegativeResult = true;
    }

解法二的时间会优化为0ms。

img

当然,这并不是很重要了。

扩展知识点

这里补充一些本题涉及到的相关知识点。

位运算符

二进制中位相关的运算符。按照我的理解,位运算符就是对每个位之间的运算。

  1. &运算符:与运算符,两个对应的位都为1时,结果为1,否则为0。
  2. |运算符:或运算符,两个对应的位都为0时,结果为0,否则为1。
  3. ^运算符:异或运算符,两个对应的位相同时为1,不同为0。
  4. ~运算符:取反运算符,对每个位取反。
  5. <<左移运算符:二进制数的每一位都向左移动n位,低位补0。数值大小变为原来的2的n次方倍。
  6. >>右移运算符:二进制数的每一位都向右移动n位,如果该数为正,则高位补0,若为负数,则高位补1。移出的部分舍弃。数值大小缩小为原来的2的n次方倍(舍弃余数)。
  7. >>>无符号右移运算符:也叫逻辑右移,二进制数的每一位都向右移动n位,即若该数为正,则高位补0,而若该数为负数,则右移后高位同样补0。

运算符优先级

位运算符优先级低于加减,因此在代码中,需要将位运算括起来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白码上飞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值