[剑指 Offer II] 整数-001. 整数除法

又开始刷题啦,先来点简单的吧

1. 题目

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

注意:

  • 整数除法的结果应当截去(truncate)其小数部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2
  • 假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−2^31, 2^31−1]。本题中,如果除法结果溢出,则返回 2^31 − 1

示例1:

输入:a = 15, b = 2
输出:7
解释:15/2 = truncate(7.5) = 7

示例2:

输入:a = 7, b = -3
输出:-2
解释:7/-3 = truncate(-2.33333..) = -2

示例3:

输入:a = 0, b = 1
输出:0

示例4:

输入:a = 1, b = 1
输出:1

提示:

  • -2^31 <= a, b <= 2^31 - 1
  • b != 0

2. 题解

题目要求我们模拟除法运算,条件是不能使用除号“/”、乘号“*”、取余号“%”,并且规定了环境只能存储32位有符号整数,即取值范围是[−2^31, 2^31−1],并规定如果结果溢出则返回2^31 − 1

2.1 减法代替除法

由于不能使用除号,我们很容易可以想到可以用减法来代替

举例:

15/2 = 7……1
可以看成15减去7个2余数为1

这样子我们就可以写出核心的部分了,上代码

var divide = function(dividend, divisor) {
    // 当被除数大于除数时,持续减去除数,用res累计结果
    let res = 0
    while (dividend >= divisor) {
        dividend -= divisor;
        res++;
    }
    return res;
}

这里我们会遇到几个问题:

  1. 只适用于正数除法
  2. 没有处理边界问题,会导致结果溢出:例如当被除数为-231**,除数为**-1**时,结果为**231,超出了最大范围2^31 - 1
  3. 效率很低,我们一个一个减去除数,如果被除数很大,需要循环很多遍

2.2 解决符号问题,以及处理边界

除法运算的正负取决于被除数和除数的正负,当其中仅有一个是负数是结果才会是负数,其余情况结果为正数,因此我们可以考虑将两个数都转为正数或者负数,最后再得出运算结果后再确定符号,那么转为正数还是负数呢?

转为负数比较好

原因还是因为取值范围的问题,当一个数是-2^31次方时,转为正数会溢出,而转为负数则不会有这种情况,上代码

var divide = function(dividend, divisor) {
    let INT_MAX = 2 ** 31 - 1;
    let INT_MIN = - ( 2 ** 31 );
    // 处理边界问题
    if (dividend === INT_MIN && divisor === -1) return INT_MAX;
    // 用于确定符号,为1是是负,其他为正
    let sign = 2;
    if (dividend > 0) {
        dividend = -dividend;
        sign--;
    }
    if (divisor > 0) {
        divisor = -divisor;
        sign--;
    }
    // 当被除数大于除数时,持续减去除数,用res累计结果
    let res = 0
    while (dividend >= divisor) {
        dividend -= divisor;
        res++;
    }
    return sign === 1 ? -res : res;
}

2.3 提高效率

比较困难的地方在这里,之前我们是一个一个除数得减去,结果每次加1,那我们可以考虑一次减去多个除数,结果每次加上多个除数的个数

具体思路是:

  1. 让除数不停得翻倍,直到翻倍结果刚好小于被除数,例如:被除数是15,除数是2,2 * 2 = 4, 4 * 4 = 8, 8 * 8 = 16, 16大于15了,说明8才是刚好小于被除数的那个数。
  2. 8是由4个2组成的,我们把4记录下来,再把被除数减去上面的结果,也就是15 - 8 = 7,结果res加上4
  3. 将7作为被除数,重复步骤1和步骤2:2 * 2 = 4, 4 * 4 = 8, 8大于7了,所以取4,4是由2个2组成,结果res加上2,7 - 4 = 3,
  4. 把3作为被除数,重复步骤1和步骤2: 2 * 2 = 4, 4大于3,所以取1,结果res加上1,结束循环,最终结果为7

注意:需要注意的是我们将除数翻倍作为判断条件,除数翻倍的结果也是有可能越界的,因此应作具体判断,我们看代码吧

var divide = function(dividend, divisor) {
    let INT_MAX = 2 ** 31 - 1;
    let INT_MIN = - ( 2 ** 31 );
    if (dividend === INT_MIN && divisor === -1) return INT_MAX;
    let res = 0;

    let sign = 2;
    if (dividend > 0) {
        dividend = -dividend;
        sign--;
    }
    if (divisor > 0) {
        divisor = -divisor;
        sign--;
    }

    while (dividend <= divisor) {
        let value = divisor;
        // k来记录除数在一轮中的个数,初始值为1
        let k = 1
        // 为了防止越界,应让value小于-(2^31)/2,也就是-2^30
        while (value > -(2 ** 30) && dividend <= (value + value)) {
            // 除数翻倍
            value = value + value;
            // 每一轮k也翻倍
            k = k + k;
        }
        // 被除数减去 翻倍结果刚好小于被除数 的那个数
        dividend -= value;
        // 将k加入结果
        res += k;
    }
    return sign === 1 ? -res : res;
};

这道题目的讲解就到这里啦,其实还有可以优化的地方,可以考虑位运算,等我学会了日后再补上(maybe),有兴趣的同学也可以先自己研究

posted @ 2021-10-28 13:26  是棕啊  阅读(143)  评论(0编辑  收藏  举报