一.引言

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

Tips:

(1) 结果的符号只需判定除数和被除数是否同符号即可,除法统一转为两正整数相除

(2) 注意数字范围,默认取 int 

MAX_INT = int(math.pow(2, 31) - 1)
MIN_INT = int(-math.pow(2, 31))

二.对数偷懒除法

题目要求不能使用乘法,除法和mod,这里可以通过对数减法达到除法的效果:

def logarithmDivide(_dividend, _divisor):
    st = time.time()
    if _divisor == 0:
        return MAX_INT
    if _dividend == 0:
        return 0
    # 判断正负号
    isPositive = (_dividend < 0) == (_divisor < 0)
    # 取绝对值
    m = abs(_dividend)
    n = abs(_divisor)
    res = math.log(m) - math.log(n)
    res = int(math.exp(res))
    print("对数除法耗时: ", (time.time() - st))
    if isPositive:
        return min(res, 2147483647)
    return max(0 - res, -2147483648)

三.递减法

除法本身其实是减法,可以通过逐级递减获取最终的值:

(1) result = 0

(2) dividend -= divisor; result + 1

(3) 直到 dividend 小于 divisor,退出

def oneByOneDivide(_dividend, _divisor):
    st = time.time()
    if _divisor == 0:
        return MAX_INT
    if _dividend == 0:
        return 0
    # 判断正负号
    result = 0
    isPositive = (_dividend < 0) == (_divisor < 0)
    while _dividend >= _divisor:
        _dividend -= _divisor
        result += 1
    print("递减除法耗时: ", (time.time() - st))
    if isPositive:
        return min(result, 2147483647)
    return max(0 - result, -2147483648)

四.递减法 Plus

递减法中被除数每次减掉一个除数,当被除数 >> 除数时,程序执行时间很慢,因此可以适当将除数放缩,加快收敛速度:

(1) result = 0,k=1

(2) dividend -= divisor; result + k

(3) divisor *= 2,k *= 2 dividend -= divisor,result += k

(4) divisor 足够大是退出第一个 while,重新从 k=1 开始

(5) dividend 足够小时退出

def multipleDivide(_dividend, _divisor):
    st = time.time()
    if _divisor == 0:
        return MAX_INT
    if _dividend == 0:
        return 0
    # 判断正负号
    result = 0
    isPositive = (_dividend < 0) == (_divisor < 0)
    tmp_dividend = _dividend
    tmp_divisor = _divisor
    while tmp_dividend >= tmp_divisor:
        k = 1
        tmp = tmp_divisor
        while tmp_dividend >= tmp:
            tmp_dividend -= tmp
            result += k
            k += k
            tmp += tmp
    print("倍数除法耗时: ", (time.time() - st))
    if isPositive:
        return min(result, 2147483647)
    return max(0 - result, -2147483648)

五.递减法 Plussss

上面采用 2 作为递增的倍数,达到了快速收敛的速度,但是当 dividend >> divisor 时,2 指数增长也需要一定步数,此时可以给启动值一个大一点的除数,提高前面的收敛速度,后面被 dividend 和 divisor 差距不大时再退化为 2 指数增长:

def multipleDivideV2(_dividend, _divisor, factor):
    st = time.time()
    if _divisor == 0:
        return MAX_INT
    if _dividend == 0:
        return 0
    # 判断正负号
    result = 0
    isPositive = (_dividend < 0) == (_divisor < 0)
    tmp_dividend = _dividend
    tmp_divisor = _divisor
    while tmp_dividend >= tmp_divisor:
        k = 1
        tmp = tmp_divisor
        # 加大力度安排
        while tmp_dividend >= factor * tmp:
            tmp_dividend -= factor * tmp
            result += factor * k
            k += factor * k
            tmp += factor * tmp
        # 退化为2倍数增长收尾
        while tmp_dividend >= tmp:
            tmp_dividend -= tmp
            result += k
            k += k
            tmp += tmp
    print("倍数除法耗时: ", (time.time() - st))
    if isPositive:
        return min(result, 2147483647)
    return max(0 - result, -2147483648)

六.总结

给定一个默认除法函数:

def commonDivide(_dividend, _divisor):
    st = time.time()
    re = _dividend / _divisor
    print("正常除法耗时: ", (time.time() - st))
    return int(re)

然后比较一个默认除法和上面几种方法的速度差异:

if __name__ == '__main__':
    dividend = 265783921
    divisor = 3
    print(commonDivide(dividend, divisor))
    print(logarithmDivide(dividend, divisor))
    print(oneByOneDivide(dividend, divisor))
    print(multipleDivide(dividend, divisor))
    print(multipleDivideV2(dividend, divisor, 1000))

速度:common >  logarithm > 加大力度 > 2倍数 >> oneByOne 

初始除数给定恰当时,多倍数增长会优于2倍数增长;给的过大或过小时,两者速度相近

正常除法耗时:  1.1920928955078125e-06
88594640
logarithm 对数除法耗时:  3.0994415283203125e-06
88594640
oneByOne 递减除法耗时:  3.9744768142700195
88594640
2倍数增长 倍数除法耗时:  1.7881393432617188e-05
88594640
加大力度安排 倍数除法耗时:  6.9141387939453125e-06
88594640