LeetCode 29|两数相除:不用 /、*,如何在 int 边界内完成除法?

5 阅读4分钟

一、题目要求

给定两个整数 dividend(被除数)和 divisor(除数),要求计算:

dividend / divisor

并返回向 0 截断后的整数结果

题目有三个关键限制:

  1. 不能使用 */% 运算符
  2. 结果需要考虑 32 位有符号整数范围
  3. 如果结果溢出,返回 Integer.MAX_VALUE

典型示例:

输入:dividend = 10, divisor = 3
输出:3

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

二、解题思路总览

这道题的难点并不在“除法本身”,而在三个地方:

  1. 不能用除法
  2. 不能溢出
  3. 不能超时

整体思路可以拆成三步:

1. 特殊溢出情况提前处理

Integer.MIN_VALUE / -1

数学结果是 2147483648,已经超过 int 最大值,必须单独处理。

2. 统一使用负数进行计算

原因只有一个:
Integer.MIN_VALUE 没有对应的正数,但负数范围更大,能避免溢出。

3. 用「倍增减法」模拟除法

核心思想:

  • 除法本质是:被除数里能减去多少个除数

  • 与其一次减一个,不如:

    • 每次把除数翻倍
    • 一次性减掉最大的那一块
  • 类似二进制拆分,时间复杂度从 O(n) 优化到 O(log n)


三、完整代码

class Solution {
    public int divide(int dividend, int divisor) {

        // 1. 处理溢出情况
        if (dividend == Integer.MIN_VALUE && divisor == -1) {
            return Integer.MAX_VALUE;
        }

        // 2. 判断结果符号
        boolean negative = (dividend < 0) ^ (divisor < 0);

        // 3. 统一转为负数,避免溢出
        int a = dividend > 0 ? -dividend : dividend;
        int b = divisor > 0 ? -divisor : divisor;

        int result = 0;

        // 4. 倍增减法
        while (a <= b) {
            int temp = b;
            int count = 1;

            // 尝试不断翻倍
            while (temp >= (Integer.MIN_VALUE >> 1) && a <= temp + temp) {
                temp += temp;
                count += count;
            }

            a -= temp;
            result += count;
        }

        return negative ? -result : result;
    }
}

四、逐行分析代码在做什么

1. 为什么要先判断这一句?

if (dividend == Integer.MIN_VALUE && divisor == -1) {
    return Integer.MAX_VALUE;
}

因为:

  • Integer.MIN_VALUE = -2147483648
  • -2147483648 / -1 = 2147483648
  • 超过 int 能表示的最大值

这是唯一一个必然溢出的情况,必须提前返回。


2. 这一句在判断什么?

boolean negative = (dividend < 0) ^ (divisor < 0);

^ 是异或运算:

  • 一个正,一个负 → 结果为负
  • 同为正或同为负 → 结果为正

这里只是记录最终结果的符号,不参与具体计算。


3. 为什么全部转成负数?

int a = dividend > 0 ? -dividend : dividend;
int b = divisor > 0 ? -divisor : divisor;

原因很关键:

  • Integer.MIN_VALUE 不能取相反数
  • 负数区间是 [-2147483648, -1],比正数多一个
  • 用负数可以保证整个计算过程不溢出

从这一行开始:

  • a 表示被除数(负数)
  • b 表示除数(负数)

4. 外层 while:为什么是 a <= b

while (a <= b) {

注意:现在全是负数

  • a <= b 等价于:|a| >= |b|
  • 说明被除数还至少能减去一个除数

5. temp 和 count 是干什么的?

int temp = b;
int count = 1;

含义:

  • temp:当前这一轮要减掉的「除数倍数」
  • count:对应这次减法,商要加多少

初始状态表示:

减掉 1 个 divisor
商增加 1

6. 这层 while 是整道题的核心

while (temp >= (Integer.MIN_VALUE >> 1) && a <= temp + temp) {
    temp += temp;
    count += count;
}

逐个条件解释:

temp >= (Integer.MIN_VALUE >> 1)

  • Integer.MIN_VALUE >> 1 = -1073741824
  • 这一步是 防止 temp + temp 溢出
  • 确保下一次翻倍仍在 int 范围内

a <= temp + temp

  • 判断被除数还能不能减掉「翻倍后的 temp」
  • 如果能,就继续扩大这一块

翻倍本身:

temp += temp;    // 除数 × 2
count += count;  // 商 × 2

这是典型的倍增思想,每次把能减掉的最大块找出来。


7. 真正“做除法”的地方

a -= temp;
result += count;

含义非常直观:

  • 从被除数中减掉这一大块
  • 商累加对应的倍数

8. 最后恢复符号

return negative ? -result : result;

前面一直在算「绝对值意义下的商」,这里统一处理正负。


五、总结

这道题本质考察的不是除法,而是三种能力:

  1. 边界意识

    • 明确 Integer.MIN_VALUE 的特殊性
  2. 数学等价转换

    • 除法 → 减法 → 倍增减法
  3. 二进制与溢出控制

    • >> 1 的真正含义
    • 为什么要统一用负数

当你真正理解这道题时,你学到的不是一个技巧,而是:

在强约束条件下,如何把一个问题“拆解、转化、再重组”。

这也是这道题成为经典面试题的原因。