剑指Offer|LCR 001. 两数相除

LCR 001. 两数相除

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

注意:

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

示例 1:

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

法1:转为负数计算

分析:不能使用乘号和除号,只能使用减法。比如计算15/2(15是被除数,2是除数),就是15不停地减2,当减去7个2后余数是1,此时不能减去更多的2,因此15/2的商为7。

这个解法存在一个问题。当被除数很大除数很小,比如 ( 2 31 − 1 ) / 1 (2^{31}-1)/1 (2311)/1,减1操作需要执行 2 31 − 1 2^{31}-1 2311次,需要很长时间。

将上述解法稍作调整。当被除数大于除数的时候,继续比较判断被除数是否大于除数的2倍,如果是,则继续判断被除数是否大于除数的4倍、8倍等。如果被除数最多大于除数的 2 k 2^k 2k,那么将被除数减去除数的 2 k 2^k 2k倍,然后将剩余的被除数重复前面的步骤。每次将除数翻倍,因此优化后的时间复杂度为 O ( l o g n ) O(logn) O(logn)

看15/2讨论计算过程。15大于2,15也大于4,15大于8,但15小于16。于是先将15-8=7,由于减去的8是除数的4倍,所以这部分的商为4,结果加4;看7/2,7大于2,7大于4,7小于8,所以7-4=3,减去的4是除数的2倍,所以这部分的商为2,结果加2;看3/2,3大于2,3小于4,所以3-2=1,减去的2是除数的1倍,所以这部分的商为1,结果加1;最后的1比除数小,不能再减去除数了,所以结果为4+2+1=7。

上述讨论的是正整数,对于负数可都转为正数,算好之后再调整正负号。例如-15/2,先算15/2得到7,结果再加上符号。

将负数转换成正数存在一个问题,对于32位正数,最小整数 − 2 31 -2^{31} 231,最大的整数是 2 31 − 1 2^{31}-1 2311。因此最小整数转成正数 2 31 2^{31} 231会溢出。由于将任意正数转换为负数不会溢出,因此可以先将正数都转换成负数,再计算商,再调整商的正负号。

最后讨论可能的溢出。由于是整数的除法,并且除数不为0,因此商的绝对值一定小于或等于被除数的绝对值。因此,int型整数的除法只有一种情况会导致溢出,即 ( − 2 31 ) / ( − 1 ) (-2^{31})/(-1) (231)/(1)。这是因为最大的正数为 2 31 − 1 2^{31}-1 2311 2 31 2^{31} 231超出了正数的范围。

  • 时间复杂度O(log a)
  • 空间复杂度O(1)
var divide = function(a, b){
    // 解决溢出问题
    if(a === -(2**31) && b === -1) return 2**31-1;
    
    let negative = 2; // 计数变符号操作次数
    // 将a和b都变为负数
    if(a > 0){
        negative--;
        a = -a;
    }
    if(b > 0){
        negative--;
        b = -b;
    }
    let result = divideCore(a, b);
    return negative === 1 ? -result : result; // negative为1的话,就说明只有1个数是正数,所以结果要加负号
  }
    
function divideCore(a, b) {
    let result = 0;

    // 当 a 小于等于 b 时,继续除法运算
    // (-15)/(-2)
    while (a <= b) {
        let value = b;
        let quotient = 1;

        // 通过倍增值和商来加速减法运算
        while (value >= -(2**30) && a <= value + value) {
            quotient += quotient;
            value += value;
        }

        result += quotient;
        a -= value;
    }
    return result;
}

来看divideCore函数,假设计算divideCore(-15, -2)

符合a <= b,value=-2,quotient=1,

value没有溢出&a也是符合条件的,quotient=2,value=-4;quotient=4,value=-8;不符合a <= value + value,跳出循环。

result=4,a=-15-(-8)=-7

计算(-7)/(-2)。符合a <= b,value=-2,quotient=1,

value没有溢出&a也是符合条件的,quotient=2,value=-4;不符合a <= value + value,跳出循环。

result=4+2=6,a=-7-(-4)=-3

计算(-3)/(-2)。符合a <= b,value=-2,quotient=1,

value没有溢出&a也是符合条件的,不符合a <= value + value,跳出循环。

result=6+1=7,a=-3-(-2)=-1;此时a=-1,b=-2,a>b跳出循环。

法2:转为正数计算

分析:和法1一样,这里是将所有数转为正数计算。其他基本一致

  • 时间复杂度O(log a)
  • 空间复杂度O(1)
var divide = function(a, b) {
    // 解决溢出问题
    if (a === -(2 ** 31) && b === -1) return 2 ** 31 - 1;
    
    // 判断符号
    let sign = (a > 0) ^ (b > 0);  // ^异或:同号0,异号1
    
    // 取正值
    a = a > 0 ? a : -a;
    b = b > 0 ? b : -b;
    
    let result = 0;

    // 进行除法计算
    while (a >= b) {
        let value = b;
        let k = 1;
        
        // 优化:倍增b,直到 value 加倍大于a
        while (a >= (value + value)) {
            value += value;  // b 值加倍
            k += k;  // k 值也加倍
        }

        // 扣除当前的倍数,增加结果
        a -= value;
        result += k;
    }

    // 返回结果,根据符号判断
    return sign ? -result : result;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

阿月浑子の

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

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

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

打赏作者

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

抵扣说明:

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

余额充值