Leetcode 剑指 Offer II 001. 整数除法

题目难度: 简单

原题链接

休息了一个月, 我又满血回来了, 今天我们来开始一个新的系列 - Leetcode 的剑指 Offer(专项突击版)!!!

它相比剑指 Offer (Easy~Medium) 或者程序员面试金典 (Medium~Hard), 难度都要大一些 (Medium+~Hard), 更适合进阶, 但考虑到现在越来越卷的面试, 推荐程度更高了, 6 颗星!!! ✨✨✨✨✨✨

如果你是萌新, 可以先看看其他两个系列哦, 公众号里回复剑指offer面试金典关键字就能看到了

这个系列的更新频率还是老样子: 每周日晚上的 6 点 45 分, 大家可以多多关注哦

另外大家在我的公众号"算法精选"中的聊天框中回复 剑指offer2 就能看到新系列当前已经更新的文章了

题目描述

  • 给定两个整数 a 和 b ,求它们的除法的商 a/b ,要求不得使用乘号 ‘*’、除号 ‘/’ 以及求余符号 ‘%’ 。
  • 注意:
    • 整数除法的结果应当截去(truncate)其小数部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2
    • 假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−2^31, 2^31−1]。本题中,如果除法结果溢出,则返回 2^31 − 1

示例 1:

  • 输入:a = 15, b = 2
  • 输出:7
  • 解释:15/2 = truncate(7.5) = 7

示例 2:

  • 输入:a = 7, b = -3
  • 输出:-2
  • 解释:7/-3 = truncate(-2.33333…) = -2

示例 3:

  • 输入:a = 0, b = 1
  • 输出:0

示例 4:

  • 输入:a = 1, b = 1
  • 输出:1

提示:

  • -2^31 <= a, b <= 2^31 - 1
  • b != 0

题目思考

  1. 除法的本质是什么?
  2. 可以利用哪些其他操作符?

解决方案

思路

  • 分析题目, 一个直观的思路就是用减法代替除法: 循环减去除数并计数, 直到被除数小于除数为止, 最终计数就是商
  • 但这种方法复杂度跟商的大小成正比, 假设被除数是 2^31, 除数是 2, 那就要循环 2^30 次, 效率过低, 有没有其他更高效的做法呢?
  • 回顾题目要求, 它只是禁用了乘除和取模, 那除了四则运算, 我们程序员经常用到的还有位运算符, 以及它们的好伙伴: 二进制
  • 回想二进制的性质, 我们的商转成二进制的话就相当于若干个位是 1, 然后其余位是 0
  • 那么商就可以表示为多个 2 的幂的和, 例如 12(0b1100), 就相当于 8(0b1000)+4(0b0100)
  • 这样我们在进行除法的时候, 就可以不断循环, 进而将商分解, 例如 128/3 可以分解为 3*(32+8+2), 即 3*42
  • 具体做法就是初始化当前 2 的幂为 1, 并将它与除数的乘积初始化为除数, 然后将 2 的幂和除数乘积不断左移, 模拟乘 2, 直到除数乘积最接近且小于等于被除数为止
  • 此时得到的 2 的幂就可以累加到最终的商里面了, 然后将被除数减去得到的除数乘积, 继续开启新一轮的循环, 直到最终被除数小于原始除数为止
  • 另外根据题目要求, 还需要额外处理正负号以及数字范围
  • 下面代码中对上面提到的每个步骤都有详细注释, 方便大家理解

复杂度

  • 时间复杂度 O(logA): 每次循环只需要 A 的对数时间, 然后只会循环常数次
  • 空间复杂度 O(1): 只使用了几个常数空间的变量

代码

class Solution:
    def divide(self, dividend: int, divisor: int) -> int:
        # 核心思路: 2的幂和除数乘积不断左移一位, 直到最接近且小于等于被除数, 循环该过程直到被除数<除数
        # 先将数字都转为正数, 并记录商的符号
        neg = False
        if dividend < 0:
            dividend = -dividend
            neg = not neg
        if divisor < 0:
            divisor = -divisor
            neg = not neg
        res = 0
        # 注意循环条件是大于等于, 因为等于情况下仍有商, 为1
        while dividend >= divisor:
            # 2的幂初始化为1
            mi = 1
            # 除数乘积初始化为除数
            cur = divisor
            # 注意循环条件是大于等于, 相等时表示恰好整除
            while dividend >= (cur << 1):
                # 2的幂和除数乘积不断左移一位, 直到最接近且小于等于被除数
                cur <<= 1
                mi <<= 1
            # 此时的除数乘积就是最接近且小于等于被除数的数了
            # 商加上当前的2的幂
            res += mi
            # 被除数减去已经使用的除数乘积
            dividend -= cur
        # 恢复符号
        if neg:
            # 负号时需要取相反数
            res = -res
        # 注意溢出时取最大正数
        mn, mx = -(2**31), 2**31 - 1
        if res < mn or res > mx:
            return mx
        return res

大家可以在下面这些地方找到我~

我的 GitHub

我的 Leetcode

我的 CSDN

我的知乎专栏

我的头条号

我的牛客网博客

我的公众号: 算法精选, 欢迎大家关注~

你可能感兴趣的:(Leetcode,leetcode,算法,职场和发展)