LeetCode_29_中等_两数相除


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,2311] 。本题中,如果商 严格大于 2 31 − 1 2^{31} − 1 2311 ,则返回 2 31 − 1 2^{31} − 1 2311 ;如果商 严格小于 − 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<=2311
  • 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 2311
  • 当除数为 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×YX>(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_MIN
            if divisor == -1:
                return INT_MAX
        
        # 考虑除数为最小值的情况
        if divisor == INT_MIN:
            return 1 if dividend == INT_MIN else 0
        # 考虑被除数为 0 的情况
        if dividend == 0:
            return 0
        
        # 一般情况,使用二分查找
        # 将所有的正数取相反数,这样就只需要考虑一种情况
        rev = False
        if dividend > 0:
            dividend = -dividend
            rev = not rev
        if divisor > 0:
            divisor = -divisor
            rev = not rev

        # 快速乘
        def quickAdd(y: int, z: int, x: int) -> bool:
            # x 和 y 是负数,z 是正数
            # 需要判断 z * y >= x 是否成立
            result, add = 0, y
            while z > 0:
                if (z & 1) == 1:    
                    # 需要保证 result + add >= x
                    if result < x - add:
                        return False
                    result += add
                if z != 1:
                    # 需要保证 add + add >= x
                    if add < x - add:
                        return False
                    add += add
                # 不能使用除法,退位
                z >>= 1
            return True
        
        left, right, ans = 1, INT_MAX, 0
        while left <= right:
            # 注意溢出,并且不能使用除法
            mid = left + ((right - left) >> 1)
            check = quickAdd(divisor, mid, dividend)
            if check:
                ans = mid
                # 注意溢出
                if mid == INT_MAX:
                    break
                left = mid + 1
            else:
                right = mid - 1

        return -ans if rev else ans

执行用时:45 ms
消耗内存:16.45 MB

题解来源:力扣官方题解

  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值