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 次。
在此基础上,我们能想到一些优化:
然而,在题目中明确提示了,要注意 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)。
痕迹
没有过去,就没法认定现在的自己