题目
给你两个整数,被除数 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
题解
解题思路
为了方便讨论,设被除数为 x,除数为 y,商为 z, 即有 x / y = z,等价为 z * y = x,目的是在不使用乘 法、除法和取余运算的前提下找到满足该等式的 z。
由于 x 和 y 都是可正可负的,如果直接处理,则需要考虑四种情况。我们可以先把 x 和 y 转换为同号后再 开始计算,这样就无需单独考虑符号的问题。 由于转为正数有可能会溢出,所以把 x 和 y 都转换为负数处理。
在 x 和 y 均为负数,这样就有 z * y >= x(因为题目说明了商是向零取整的),所以问题就转换为了找到满足该公式的最大的 z,此时 z 也就是商。由于 z 是正数(或 0),所以查找范围也就是 1 ~ 231 - 1,如果找不到,那么就一定是 0。为了防止超时,我们需要在区间内使用二分查找算法找到目标值。
上面的方法并未能覆盖所有情况,对于一些边界情况,我们需要提前判断直接返回结果,例如当 x 为 -231 而 y 为 -1 时,结果是会溢出的,此时直接返回 231 - 1 即可,同时这里也相当于剪枝可以提高效率。
此外,对于 z * y >= x 也是一个难点,因为不能使用乘法。我们可以使用 快速乘 算法解决,基于二进制分解原理,将乘法运算转化为加法和位移操作。核心思想是将乘数 z 分解为二进制形式,然后根据每一位是否为 1 来决定是否将对应的 y 的倍数加到结果中。
在快速乘实现中,为了提高效率以及防止溢出,要加上剪枝判断,例如当前累加和 res 要加上 add 前先判断一下是否会超出范围,是就直接返回结果即可。注意,为了防止溢出,判断也要注意一些细节。例如要判断 res + add < x,由于 res + add 有可能会溢出,所以需要改为判断 res < x - add 即可(此处 res 和 add 均为负数,两个负数之差不可能会溢出)。
代码
class Solution {
public int divide(int dividend, int divisor) {
// 先处理边界情况
if (dividend == Integer.MIN_VALUE) {
if (divisor == -1) {
return Integer.MAX_VALUE;
}
if (divisor == 1) {
return Integer.MIN_VALUE;
}
if (divisor == Integer.MIN_VALUE) {
return 1;
}
}
if (divisor == Integer.MIN_VALUE) {
return 0;
}
if (dividend == 0) {
return 0;
}
// 被除数和除数均转换为负数
boolean reverse = false;
int x = dividend;
int y = divisor;
if (dividend > 0) {
x = -dividend;
reverse = !reverse;
}
if (divisor > 0) {
y = -divisor;
reverse = !reverse;
}
// 普通情况就使用二分查找得到答案
// dividend / divisor = z,即 z * divisor >= dividend,满足该条件的最大的 z 即为答案
int left = 1;
int right = Integer.MAX_VALUE;
int ans = 0;
while (left <= right) {
// 1. 要防止溢出 2. 不允许使用除法
int mid = left + ((right - left) >> 1);
if (checkByQuickAdd(x, y, mid)) {
ans = mid;
// 注意溢出
if (mid == Integer.MAX_VALUE) {
break;
}
left = mid + 1;
} else {
right = mid - 1;
}
}
return reverse ? -ans : ans;
}
/**
* 通过快速乘法判断 z * y >= x 是否成立
*/
private boolean checkByQuickAdd(int x, int y, int z) {
int res = 0;
int add = y;
while (z != 0) {
if ((z & 1) != 0) {
if (res < x - add) {
return false;
}
res += add;
}
// 更新 add
if (z != 1 && add < x - add) {
return false;
}
add += add;
z >>= 1;
}
return true;
}
}
复杂度分析
-
时间复杂度:O(log² C) 其中 C 为 32 位整数的最大值。 二分查找时间复杂度为 O(log C),每次使用快速乘判断 z * y >= x 的时间复杂度也为 O(log C)(也可视为 O(32)),所以总时间复杂度为 O(log² C)。
-
空间复杂度:O(1)
优质项目推荐
推荐一个可用于练手、毕业设计参考、增加简历亮点的项目。
lemon-puls/txing-oj-backend: Txing 在线编程学习平台,集在线做题、编程竞赛、即时通讯、文章创作、视频教程、技术论坛为一体