29. 两数相除

184 阅读2分钟

题目描述

给定两个整数,被除数 dividend 和除数 divisor。将两数相除,要求不使用乘法、除法和 mod 运算符。
返回被除数 dividend 除以除数 divisor 得到的商。
整数除法的结果应当截去(truncate)其小数部分,例如:truncate(8.345) = 8 以及truncate(-2.7335) = -2

示例

示例 1:
输入: dividend = 10, divisor = 3
输出: 3
解释: 10/3 = truncate(3.33333..) = truncate(3) = 3

示例 2:
输入: dividend = 7, divisor = -3
输出: -2
解释: 7/-3 = truncate(-2.33333..) = -2

来源:力扣(LeetCode)
链接:leetcode-cn.com/problems/di…

分析

根据题目要求不使用乘法、除法和mod运算,首先想到了位运算(循环加减法太慢了,不考虑)和转换除法运算为加减两个思路。
我们先考虑第二种转换除法,将其转换为exp(log(a)-log(b))的形式,公式与a/b一样,但是实际计算机执行中先进行log(a)和log(b),然后相减,最后再执行exp(x),中间经历几次除不尽导致结果与实际可能偏差。如-231/3=-77,如果使用上述办法求出的是-76。因此我们考虑用位运算:

任何一个数k可以被表示为:k = b0 * 2^0 + b1 * 2^1 + b2 * 2^2 + ... + bn * 2^n + ... 例如231/3,b0...bn都是3,我们要求的就是需要多少个3。 例:59/3=19 以2^0 * 3为计量,需要计算19次59-3 以2^1 * 3为计量,需要计算9次59-6和1次59-3 以此类推

实现

  • 方法一:使用了强制转换为long,防止溢出
// 转化为对数做减法
int divide(int dividend, int divisor)
{
    // int范围整数比负数小1,如果除数为INT_MIN,除数为-1,那么求的值为2^31,溢出了,溢出返回2^31-1
    if (dividend == INT_MIN && divisor == -1) {
        return INT_MAX;
    }
    
    // 把负号保存下来
    int sym = (dividend < 0) ^ (divisor < 0);  // 异或运算,被除数和除数负号不相同时返回真(1)
    long x = labs((long)dividend);
    long y = labs((long)divisor);
    long result = 0;
    while (x >= y) {
        long tmp = y;
        long res = 1;
        while (x >= (tmp << 1)) {
            tmp <<= 1;
            res <<= 1;    // tmp中包含多少个y
        }
        result += res;
        x -= tmp;
    }

    if (sym == 1) {
        result *= -1;
    }

    if (result > INT_MAX) {
        return INT_MAX;
    }

    return result;
}
  • 方法二:不使用强制转换,而是提前判断边界,并且计算时右移而不是左移
// 转化为对数做减法
int divide(int dividend, int divisor)
{
    int result = 0;
    if (divisor == INT_MIN) {      // 除数边界值特殊处理
        if (dividend == INT_MIN) {
            return 1;
        } else {
            return 0;
        }
    }

    if (dividend == INT_MIN) {     // 被除数边界值特殊处理
        if (divisor == -1) {
            return INT_MAX;
        } else if (divisor == 1) {
            return INT_MIN;
        }
        dividend += abs(divisor);  // 先执行一次加操作,避免abs转换溢出
        result++;
    }

    // 把负号保存下来
    int sym = (dividend < 0) ^ (divisor < 0);  // 异或运算,被除数和除数负号不相同时返回真(1)
    int x = abs(dividend);
    int y = abs(divisor);
 
    while (x >= y) {
        int tmp = y;
        int res = 1;
        while (tmp < (x >> 1)) {
            tmp <<= 1;
            res <<= 1;    // tmp中包含多少个y
        }
        result += res;
        x -= tmp;
    }
    
    if (sym == 1) {
        result *= -1;
    }
    if (result > INT_MAX) {
        return INT_MAX;
    }
    return result;
}