Lever's Castle

29. 两数相除

March 31, 2020

https://leetcode-cn.com/problems/divide-two-integers/

解:

func divide(dividend int, divisor int) int {
    if dividend == 0 {
        return 0
    }
    if divisor == 1 {
        return dividend
    }
    if divisor == -1 {
        if dividend == -2147483648 {
            return 2147483647
        }
        return -dividend
    }
    var isSameSymbol bool
    if dividend < 0 && divisor < 0 {
        isSameSymbol = true
    } else if dividend > 0 && divisor > 0 {
        isSameSymbol = true
    }
    var dvd, dvs uint
    if dividend < 0 {
        if dividend == -2147483648 {
            dvd = 2147483648
        } else {
            dvd = uint(-dividend)
        }
    } else {
        dvd = uint(dividend)
    }
    if divisor < 0 {
        if divisor == -2147483648 {
            dvs = 2147483648
        } else {
            dvs = uint(-divisor)
        }
    } else {
        dvs = uint(divisor)
    }
    d := dvs
    bitNum := []uint{d}
    // 将 d 不断 *2,直到大于 dvd,此时 bitNum 中的最后一个值一定大于 dvd
    for d <= dvd {
        d = d << 1
        bitNum = append(bitNum, d)
    }
    var res int
    // 舍弃掉最后一个值
    i := len(bitNum) - 2
    for dvd >= dvs {
        if dvd >= bitNum[i] {
            dvd -= bitNum[i]
            // 2 ^ i 个 dvs
            res += 1 << i
        } else {
            i--
        }
    }
    if isSameSymbol {
        return res
    }
    return -res
}

题目中要求不能使用乘法、除法、mod 运算符,我们之后基于加减法去做。正常来说,被除数其实就是商个除数相加的结果。基于这一点,我们只要让被除数不断的减去除数,看看能最多能减几次,就可以得到最终的结果了。但这个方法耗时太高,比如被除数为 2147483647,除数为 1,你就要循环 2147483647 次。

在此基础上,我们能想到一些优化:

  1. 0 除任何数都为 0
  2. 任何数除以 1 结果都是其本身
  3. 任何数除以 -1 结果都与其本身相反

然而,在题目中明确提示了,要注意 int32 的范围 —— -2147483648 ~ 2147483647,因此我们要特殊处理下 -2147483648 取反的情况,不能让它溢出,又要确保计算结果正确。题目中有个点不要忽略:本题中,如果除法结果溢出,则返回 (2^31) − 1。注意,这里的溢出情况处理只针对结果。

为了便于处理,我们需要把除数和被除数统一处理成正数,我们只需要单独记录下两数的符号是否一致即可,相同最后就能直接返回结果,不同最后就要进行取反操作。

为了处理 -2147483648 取反的情况,这里直接使用了 uint 来代替 int,以扩展正数的范围。

接下来的处理是重中之重,如果我们按照前面说的算法进行处理,一旦遇到被除数和除数差距悬殊的情况,就需要耗费大量的时间进行循环计算。这里我们可以考虑做一些优化:一次一次的减,变成一批一批的减(比如 51 / 3 的计算过程中,原来一次减一个 3,我们要减 17 次,现在我们只要能一次减 17 个 3,就只需要减 1 次)。

我们可以先找到被除数最多一次减多少个除数。由于这里不能使用乘法,我们只好通过左移操作来完成除数的扩大,扩大到刚好大于被除数为止。我们把每次位移的结果存到一个数组中,让被除数从大到小不断的减,直到它的值比除数还要小(减不动了)。至于除法运算的结果,我们需要通过计算减了几次来得出。假设 i 为数组的索引,则数组中的每个值可以用:除数 * 2 ^ i 来表示,由此可知,如果我们要计算第 i 个元素代表多少个除数,可以直接通过 2 ^ i 得出,即 (1 << i)。


Lever

痕迹
没有过去,就没法认定现在的自己