《剑指 Offer》专项突破版 - 面试题 1 : 整数除法

题目链接29. 两数相除 - 力扣(LeetCode)

题目

输入两个 int 型整数(整数范围为 -2^31 ~ 2^31 - 1​),它们进行除法计算并返回商,要求不得使用乘号 '*'、除号 '/' 及求余符号 '%'。当发生溢出时,返回最大的整数值。假设除数不为 0。例如,输入 15 和 2,输出 15 / 2 的结果,即 7。

分析

这个题目限制我们不能使用乘号和除号进行运算。一个直观的解法是基于减法实现除法。例如,为了求得 15 / 2 的商,可以不断地从 15 里减去 2,当减去 7 个 2 之后余数是 1,此时不能减去更多的 2,因此 15 / 2 的商是 7。我们可以用一个循环实现这个过程。

但这个直观的解法存在一个问题。当被除数很大但除数很小时,减法操作执行的次数会很多。例如,求 (2^31 - 1) / 1,减 1 的操作将执行 2^31 - 1 次,需要很长时间。如果被除数是 n,那么这种解法的时间复杂度为 O(n)。我们需要对这种解法进行优化。

可以将上述解法稍作调整。当被除数大于除数时,判断被除数是否大于除数的 2 倍,如果是,则继续判断被除数是否大于除数的 4 倍、8 倍等。如果被除数最多大于除数的 2^k 倍,那么将被除数减去除数的 2^k 倍,然后将剩余的被除数重复前面的步骤。由于每次将除数翻倍,因此优化后的时间复杂度为 O(logn)。

下面以 15 / 2 为例讨论计算的过程。15 大于 2,也大于 2 的 2 倍(即 4),还大于 2 的 4 倍(即 8),但小于 2 的 8 倍(即 16),于是先将 15 减去 8,还剩余 7。由于减去的是除数的 4 倍,减去这部分对应的商是 4。接下来对剩余的 7 和除数 2 进行比较,7 大于 2,大于 2 的 2 倍(即 4),但小于 2 的 4 倍(即 8),于是将 7 减去 4,还剩余 3。这一次减去的是除数的 2 倍,对应的商是 2。然后对剩余的 3 和除数 2 进行比较,3 大于 2,但小于 2 的 2 倍(即 4),于是将 3 减去 2,还剩余 1。这一次减去的是除数的 1 倍,对应的商是 1。最后剩余的数字是 1,比除数小,不能再减去除数了。于是 15 / 2 的商是 4 + 2 + 1,即 7

上述讨论假设被除数和除数都是正整数,如果有负数则可以将它们转化成正数,计算正数的除法之后再根据需要调整商的正负号。例如,如果计算 -15 / 2,则可以先计算 15 / 2,得到的商是 7。由于被除数和除数中有一个负数,因此商应该是负数,于是商应该是 -7

将负数转换成正数存在一个小问题。对于 32 位的整数而言,最小的整数是 -2^31,最大的整数是 2^31 - 1,因此,如果将 -2^31 转换为正数则会导致溢出。由于将任意正数转换为负数都不会溢出,因此可以先将正数都转换成负数,用前面优化之后的减法计算两个负数的除法,然后根据需要调整商的正负号

最后讨论可能的溢出。由于是整数的除法并且除法不等于 0,因此商的绝对值一定小于或等于被除数的绝对值。因此,int 型整数的除法只有一种情况会导致溢出,即 (-2^31) / (-1),这是因为最大的正数为 2^31 - 1,2^31 超出了正数的范围

代码实现

class Solution {
public:
    int divide(int dividend, int divisor) {
        if (dividend == 0x80000000)
        {
            if (divisor == -1)
                return 0x7fffffff;

            if (divisor == 1)
                return 0x80000000;
        }

        int negativeNum = 2;
        if (dividend > 0)
        {
            --negativeNum;
            dividend = -dividend;
        }
        if (divisor > 0)
        {
            --negativeNum;
            divisor = -divisor;
        }

        // 注意:计算两个负数的除法
        int result = 0;
        while (dividend <= divisor)
        {
            int tmp = divisor;
            int quotient = 1;
            // 注意:不得使用乘号 *
            while (tmp >= 0xc0000000 && dividend <= tmp + tmp)
            {
                quotient = quotient + quotient;
                tmp = tmp + tmp;
            }
            dividend -= tmp;
            result += quotient;
        }

        return negativeNum == 1 ? -result : result;
    }
};

注意

  1. 0x80000000 即 -2^31

  2. 0x7fffffff 即 2^31 - 1

  3. 0xc0000000 即 -2^30,是 -2^31 的一半。  

  • 15
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值