文章目录
- 1. 题目
- 2. 思路及代码实现详解(Python)
- 2.1 位运算与二分查找
1. 题目
给你两个整数,被除数 d i v i d e n d dividend dividend 和除数 d i v i s o r divisor divisor。将两数相除,要求 不使用 乘法、除法和取余运算。
整数除法应该向零截断,也就是截去( t r u n c a t e truncate truncate)其小数部分。例如, 8.345 8.345 8.345 将被截断为 8 8 8 , − 2.7335 -2.7335 −2.7335 将被截断至 − 2 -2 −2。
返回被除数 d i v i d e n d dividend dividend 除以除数 d i v i s o r divisor divisor 得到的 商 。
注意:假设我们的环境只能存储 32 32 32 位 有符号整数,其数值范围是 [ − 2 31 , 2 31 − 1 ] [−2^{31}, 2^{31} − 1] [−231,231−1] 。本题中,如果商 严格大于 2 31 − 1 2^{31} − 1 231−1 ,则返回 2 31 − 1 2^{31} − 1 231−1 ;如果商 严格小于 − 2 31 -2^{31} −231 ,则返回 − 2 31 -2^{31} −231 。
示例 1:
输入: d i v i d e n d = 10 , d i v i s o r = 3 dividend = 10, divisor = 3 dividend=10,divisor=3
输出: 3 3 3
解释: 10 / 3 = 3.33333.. 10/3 = 3.33333.. 10/3=3.33333..,向零截断后得到 3 3 3。
示例 2:
输入: d i v i d e n d = 7 , d i v i s o r = − 3 dividend = 7, divisor = -3 dividend=7,divisor=−3
输出: − 2 -2 −2
解释: 7 / − 3 = − 2.33333.. 7/-3 = -2.33333.. 7/−3=−2.33333.. ,向零截断后得到 − 2 -2 −2。
提示:
- − 2 31 < = d i v i d e n d , d i v i s o r < = 2 31 − 1 -2^{31} <= dividend, divisor <= 2^{31} - 1 −231<=dividend,divisor<=231−1
- d i v i s o r ≠ 0 divisor \neq 0 divisor=0
2. 思路及代码实现详解(Python)
由于题目规定了「只能存储 32 32 32 位整数」,因此不能使用任何 64 64 64 位整数,尽管这会极大增加我们编码难度。对于可能造成溢出的问题,在编码之前,需要先对于溢出或者容易出错的边界情况进行讨论,即在某一边界上,继续进行操作后会溢出,这个边界取决于我们会采取何种操作:
- 当被除数为 32 32 32 位有符号整数的最小值 − 2 31 −2^{31} −231 时:
- 如果除数为 1 1 1,那么我们可以直接返回答案 − 2 31 −2^{31} −231;
- 如果除数为 − 1 −1 −1,那么答案为 2 31 2^{31} 231,这时结果产生了溢出。此时我们需要返回 2 31 − 1 2^{31}-1 231−1;
- 当除数为 32 32 32 位有符号整数的最小值 − 2 31 −2^{31} −231 时:
- 如果被除数同样为 − 2 31 −2^{31} −231,那么我们可以直接返回答案 1 1 1;
- 对于其余的情况,我们返回答案 0 0 0,因为得到的商不大于 1 1 1 时截断小数部分得到 0 0 0。
- 当被除数为 0 0 0 时,不论除数是多少,都直接返回答案 0 0 0。
对于其他的一般情况,根据除数和被除数的符号,我们需要考虑 4 4 4 种不同的符号组合。因此,为了方便编码,我们可以将被除数或者除数取相反数,使得它们符号相同。
- 如果将被除数和除数都变为正数,那么可能会导致溢出。例如当被除数为 − 2 31 −2^{31} −231 时,它的相反数 2 31 2^{31} 231 产生了溢出。
- 如果将被除数和除数都变为负数,这样在取相反数时就不会有溢出的问题,而结果溢出也只有 ( − 2 31 ) / ( − 1 ) (-2^{31})/(-1) (−231)/(−1) 这种情况。
如果我们恰好只将被除数和除数中的一个变为了正数,那么在返回答案之前,我们需要对答案也取相反数。
2.1 位运算与二分查找
上面的讨论考虑了数值溢出的问题,以及几种可以直接返回结果的特殊情况。我们记被除数为 X X X,除数为 Y Y Y,且此时两者都被我们转为负数,我们要求的是 X / Y X/Y X/Y 的结果 Z Z Z,显然, Z Z Z 的值一定为整数或 0 0 0。
因为 Y Y Y 为负值,随着 Z Z Z 增大乘积减小,容易得到: Z × Y ≥ X > ( Z + 1 ) × Y Z\times Y\geq X\gt (Z+1)\times Y Z×Y≥X>(Z+1)×Y,因此我们求商就是找到最大的使得 Z × Y > X Z\times Y\gt X Z×Y>X 成立的 Z Z Z。而由于不能使用乘法运算,就需要用到快速乘的算法。其实就是通过二分查找和位移运算来实现所谓的 乘法,但其本质是加法的快速运算和判断。
具体的代码如下,判断不等式的次数为 O ( l o g C ) O(logC) O(logC),而判断函数的搜索范围是 32 32 32 位整数的全部范围,最差情况下也要 O ( l o g C ) O(logC) O(logC) 的判断次数,因此总的时间复杂度为 O ( l o g 2 C ) O(log^2C) O(log2C),而空间复杂度为 O ( 1 ) O(1) O(1),仅存储若干的上下界信息。
class Solution:def divide(self, dividend: int, divisor: int) -> int:INT_MIN, INT_MAX = -2**31, 2**31 - 1# 考虑被除数为最小值的情况if dividend == INT_MIN:if divisor == 1:return INT_MINif divisor == -1:return INT_MAX# 考虑除数为最小值的情况if divisor == INT_MIN:return 1 if dividend == INT_MIN else 0# 考虑被除数为 0 的情况if dividend == 0:return 0# 一般情况,使用二分查找# 将所有的正数取相反数,这样就只需要考虑一种情况rev = Falseif dividend > 0:dividend = -dividendrev = not revif divisor > 0:divisor = -divisorrev = not rev# 快速乘def quickAdd(y: int, z: int, x: int) -> bool:# x 和 y 是负数,z 是正数# 需要判断 z * y >= x 是否成立result, add = 0, ywhile z > 0:if (z & 1) == 1: # 需要保证 result + add >= xif result < x - add:return Falseresult += addif z != 1:# 需要保证 add + add >= xif add < x - add:return Falseadd += add# 不能使用除法,退位z >>= 1return Trueleft, right, ans = 1, INT_MAX, 0while left <= right:# 注意溢出,并且不能使用除法mid = left + ((right - left) >> 1)check = quickAdd(divisor, mid, dividend)if check:ans = mid# 注意溢出if mid == INT_MAX:breakleft = mid + 1else:right = mid - 1return -ans if rev else ans
执行用时:45 ms
消耗内存:16.45 MB
题解来源:力扣官方题解