题目
给你两个整数,被除数 dividend 和除数 divisor。将两数相除,要求 不使用 乘法、除法和取余运算。
整数除法应该向零截断,也就是截去(truncate)其小数部分。例如,8.345 将被截断为 8 ,-2.7335将被截断至 -2 。
返回被除数 dividend 除以除数 divisor 得到的 商 。
注意: 假设我们的环境只能存储 32 位 有符号整数,其数值范围是 [−231, 231 − 1] 。本题中,如果商 严格大于 231 − 1 ,则返回 231 − 1 ;如果商 严格小于 -231 ,则返回 -231 。
示例 1:
输入: dividend = 10, divisor = 3
输出: 3
解释: 10/3 = 3.33333.. ,向零截断后得到 3 。
示例 2:
输入: dividend = 7, divisor = -3
输出: -2
解释: 7/-3 = -2.33333.. ,向零截断后得到 -2 。
提示:
-231 <= dividend, divisor <= 231 - 1divisor != 0
Java语言
java
class Solution {
public int divide(int dividend, int divisor) {
// 溢出处理:唯一会溢出的情况
if (dividend == Integer.MIN_VALUE && divisor == -1) {
return Integer.MAX_VALUE;
}
// 记录符号,转为正数计算
boolean negative = (dividend < 0) ^ (divisor < 0);
long a = Math.abs((long) dividend);
long d = Math.abs((long) divisor);
int result = 0;
// 倍增法:每次尽可能多地减
while (a >= d) {
long temp = d;
long multiple = 1;
while (a >= (temp << 1)) {
temp <<= 1;
multiple <<= 1;
}
a -= temp;
result += multiple;
}
return negative ? -result : result;
}
}
核心思路: 倍增法(快速减法)。不用一个一个减,而是每次把除数翻倍,减掉尽可能大的部分,大幅加速。
逐行说明:
java
if (dividend == Integer.MIN_VALUE && divisor == -1) {
return Integer.MAX_VALUE;
}
唯一会溢出的情况:-2147483648 / -1 = 2147483648,超过 Integer.MAX_VALUE,按题目要求返回 MAX_VALUE。
java
boolean negative = (dividend < 0) ^ (divisor < 0);
用异或 ^ 判断结果是否为负:两个符号不同时为 true。
java
long a = Math.abs((long) dividend);
long d = Math.abs((long) divisor);
转成 long 再取绝对值,避免 Integer.MIN_VALUE 取绝对值溢出。
java
int result = 0;
while (a >= d) {
long temp = d;
long multiple = 1;
while (a >= (temp << 1)) {
temp <<= 1;
multiple <<= 1;
}
a -= temp;
result += multiple;
}
核心倍增逻辑:
temp:当前要减的值,从d开始multiple:当前减的值对应多少个除数
每次把 temp 翻倍,直到再翻就超过 a 了。然后减掉这个最大的 temp,累加对应的 multiple。
例如 10 / 3:
第 1 轮: temp=3, 翻倍=6 ≤ 10 ✓, 翻倍=12 > 10 停
→ 减掉 6, result += 2, a = 4
第 2 轮: temp=3, 翻倍=6 > 4 停
→ 减掉 3, result += 1, a = 1
a < 3, 结束
result = 2 + 1 = 3 ✓
java
return negative ? -result : result;
根据符号返回结果。
示例 2 演示: dividend = 7, divisor = -3
negative = true (7正, -3负, 符号不同)
a = 7, d = 3
第 1 轮: temp=3, 6≤7 ✓, 12>7 停 → 减6, result=2, a=1
a < 3, 结束
result = -2 ✓
时间复杂度 O(log n × log n),空间复杂度 O(1)。
python语言
python
class Solution(object):
def divide(self, dividend, divisor):
# 溢出处理
if dividend == -2**31 and divisor == -1:
return 2**31 - 1
# 记录符号,转为正数
negative = (dividend < 0) ^ (divisor < 0)
a = abs(dividend)
d = abs(divisor)
result = 0
while a >= d:
temp = d
multiple = 1
while a >= (temp << 1):
temp <<= 1
multiple <<= 1
a -= temp
result += multiple
return -result if negative else result
Python 不需要 long 转换,因为 Python 整数不会溢出。abs() 直接用。^ 在 Python 中也是异或。<<= 左移赋值。逻辑完全一致。
倍增法适合线性增长太慢,需要指数级加速 的场景。核心思想是:不一步一步走,而是2 倍、4 倍、8 倍...地跳,快速缩小范围。
适合的场景
1. 代替逐次减法(本题)
普通: 1000 - 3 - 3 - 3 - ... (333 次)
倍增: 1000 - 3 - 6 - 12 - 24 - 48 - 96 - 192 - 384 → 剩 235
再来一轮: 235 - 3 - 6 - 12 - 24 - 48 - 96 - 192 → 剩 43
...大约 20 次搞定
2. 快速幂(计算 a^n)
// 普通: a * a * a * ... 乘 n 次 → O(n)
// 倍增: a^16 = (a^8)^2 = ((a^4)^2)^2 = ... 只需 4 次 → O(log n)
2^10 = (2^5)^2 = (2^2 × 2^3)^2 = ...
直接算: 2×2×2×2×2×2×2×2×2×2 = 10 次
倍增法: 2^2 → 2^4 → 2^8 → 2^10,只需 4 次
3. 链表找中点 / 找环
// 快慢指针本质就是倍增思想
// 慢指针走 1 步,快指针走 2 步
// 快指针到终点时,慢指针正好在中点
4. LCA(最近公共祖先)中向上跳
// 普通做法:两个节点一步一步往上走 → O(n)
// 倍增法:预处理 2^k 步祖先,一次跳 2^k → O(log n)
5. RMQ / 区间最值
// 预处理每个位置往后 2^k 的最值
// 查询时直接 O(1) 得到区间最值
共同特征
| 特征 | 一步一步 | 倍增法 |
|---|---|---|
| 步长 | 固定 1 | 1 → 2 → 4 → 8 → ... |
| 时间 | O(n) | O(log n) |
| 本质 | 线性 | 指数 |
判断口诀
当你发现一个问题需要反复做同一种操作 ,且可以成倍地跳时,就适合用倍增法:
能减 3 → 能不能减 6 → 能不能减 12 → ...
能跳 1 步 → 能跳 2 步 → 能跳 4 步 → ...
能乘 1 次 → 能乘 2 次 → 能乘 4 次 → ...