在问 · 算法题解

29. 两数相除

中等位运算倍增减法力扣原题 ↗

题目描述

输入两个整数:被除数 dividend 和除数 divisor。不准使用乘法、除法和取余运算,算出 dividend 除以 divisor 的商并返回。

商向零取整,即直接丢掉小数部分。例如 8.345 取整为 8,-2.7335 取整为 -2。

假设环境只能存放 32 位有符号整数,范围是 [-2^31, 2^31 - 1]。商大于 2^31 - 1 时返回 2^31 - 1;商小于 -2^31 时返回 -2^31。

示例 1
输入:dividend = 10, divisor = 3
输出:3

解释:10 / 3 = 3.33...,去掉小数得 3。

示例 2
输入:dividend = 7, divisor = -3
输出:-2

解释:7 / -3 = -2.33...,向零取整得 -2。

数据范围

  • -2^31 <= dividend, divisor <= 2^31 - 1
  • divisor != 0

解题思路:倍增减法

先处理唯一会溢出的情况 -2^31 / -1,再记下结果符号,对两数取绝对值 a、b。商的二进制可以从高位往低位凑:每一轮把 b 不断翻倍(t += t,相当于左移一位),找到不超过 a 的最大 t = b·2^k,从 a 中减去 t,商加上 2^k。重复直到 a < b。

为什么正确

原被除数 = ans·b + a 始终成立;每轮至少把 a 减半,所以外层循环最多约 32 轮。

复杂度分析

时间 O(log²C),C 为数值范围(外层、内层各至多约 32 次);空间 O(1)。

易错点

-2^31 取绝对值会超出 int 范围,C++/Java 要先转成 64 位再取绝对值;-2^31 / -1 = 2^31 必须单独返回 2^31 - 1。翻倍条件写成 a >= t + t,用 64 位就不会溢出。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def divide(self, dividend: int, divisor: int) -> int:
        INT_MAX, INT_MIN = 2 ** 31 - 1, -2 ** 31
        if dividend == INT_MIN and divisor == -1:
            return INT_MAX
        neg = (dividend < 0) != (divisor < 0)
        a, b = abs(dividend), abs(divisor)
        ans = 0
        while a >= b:
            t, cnt = b, 1
            while a >= t + t:
                t += t
                cnt += cnt
            a -= t
            ans += cnt
        return -ans if neg else ans

载入编辑器,自己跑一遍 →

其他解法

倍增减法和二分 + 快速乘都是 O(log² C);类二进制拆分预先存好倍数表,从高位贪心一遍得到商,只需 O(log C)。

解法 2:类二进制拆分 时间 O(log C),空间 O(log C)

取绝对值后,先用自加得到 b、2b、4b……直到超过 a,并同步记录对应的倍数 1、2、4……。再从最大的倍数往小试:当前余数 a 不小于 2^i·b 时就减去它,并把 2^i 加进商。这相当于从高位到低位逐位确定商的二进制表示。C 是被除数的绝对值。

倍数表只算一次,每一位只试一次,时间从 O(log² C) 降到 O(log C)。需要额外保存至多 32 个倍数。

class Solution:
    def divide(self, dividend: int, divisor: int) -> int:
        INT_MAX, INT_MIN = 2 ** 31 - 1, -2 ** 31
        if dividend == INT_MIN and divisor == -1:
            return INT_MAX
        neg = (dividend < 0) != (divisor < 0)
        a, b = abs(dividend), abs(divisor)
        dbl, cnt = [b], [1]
        while dbl[-1] + dbl[-1] <= a:
            dbl.append(dbl[-1] + dbl[-1])
            cnt.append(cnt[-1] + cnt[-1])
        ans = 0
        for i in range(len(dbl) - 1, -1, -1):
            if a >= dbl[i]:
                a -= dbl[i]
                ans += cnt[i]
        return -ans if neg else ans

解法 3:二分查找 + 快速乘 时间 O(log² C),空间 O(1)

商 q 满足单调性:q·b ≤ a 对小的 q 成立,对大的 q 不成立。在 [0, a] 上二分查找最大的满足条件的 q。判断 q·b ≤ a 时不用乘法,而是按 q 的二进制位累加倍增的 b(快速乘),累加和一旦超过 a 就提前返回。

把除法转化为「二分答案 + 判定」,模板通用。每次判定要 O(log C),总时间不如类二进制拆分。

class Solution:
    def divide(self, dividend: int, divisor: int) -> int:
        INT_MAX, INT_MIN = 2 ** 31 - 1, -2 ** 31
        if dividend == INT_MIN and divisor == -1:
            return INT_MAX
        neg = (dividend < 0) != (divisor < 0)
        a, b = abs(dividend), abs(divisor)

        def fits(q):
            res, add = 0, b
            while q:
                if q & 1:
                    res += add
                    if res > a:
                        return False
                q >>= 1
                if q:
                    add += add
            return True

        lo, hi = 0, a
        while lo < hi:
            mid = lo + ((hi - lo + 1) >> 1)
            if fits(mid):
                lo = mid
            else:
                hi = mid - 1
        return -lo if neg else lo

位运算 专题的其他题目

查看 位运算 全部题目 →