给定两个整数,被除数 dividend 和除数 divisor。将两数相除,要求不使用乘法、除法和 mod 运算符。
返回被除数 dividend 除以除数 divisor 得到的商。
整数除法的结果应当截去(truncate)其小数部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/divide-two-integers
解题思路
- 被除数为0直接返回0
- 除数为1直接返回被除数
- 除数为-1时, 如果被除数是整型最小值, 则返回整型最大值(整型最小值取相反数会溢出), 否则返回被除数的相反数
- 其余结果
- 记录结果正负, 也就是看被除数和除数的正负情况, 同号为正, 反之为负
- 将被除数和除数取绝对值, 在递归方法中计算, 返回绝对值的计算结果
- 根据上面记录的正负结果, 返回最后结果
递归方法
举例:10 / 3, 比较10和3的大小, 10比3大, 则商必定大于1, 然后将3翻倍为6, 比较10和6, 还是10大, 则商必定大于2, 再将6翻倍为12, 现在是10小, 所以最终结果必定在2到4之间, 不妨将10 - 6 = 4, 比较4于3, 4大, 则结果至少为2 + 1 = 3, 将3翻倍为6, 4小于6, 于是重复类似的操作, 4 - 3 = 1, 1小于3, 直接返回2 + 1 + 0 = 3 为最终结果, 看代码会清晰得多
- 优化: 由于采用绝对值计算需要用 long 接收变量(因为整型最小值取相反数会溢出), 索性将两个数置为负数, 用负数计算就可以避免采用 long 类型接收参数, 减少开销, 不过需要注意负数的除数在"翻倍"的过程中要判断是否为负数, 因为翻倍可能使它溢出变为整数
int a = Integer.MIN_VALUE;
System.out.println(a - 1); // 整型最小值 - 1 = 整型最大值
代码
class Solution {
int divide(int dividend, int divisor) {
// 被除数为0直接返回0
if (dividend == 0) {
return 0;
}
if (divisor == 1) {
return dividend;
}
if (divisor == -1) {
return dividend == Integer.MIN_VALUE ? Integer.MAX_VALUE : -dividend;
}
// 记录结果正负的符号
int sign = 1;
if (dividend > 0 && divisor < 0 || dividend < 0 && divisor > 0) {
sign = -1;
}
// 原先采用绝对值来计算, 但是整数最小值取相反数会溢出
// 所以将数统统置为负数, 直接用两个负数计算
dividend = dividend > 0 ? -dividend : dividend;
divisor = divisor > 0 ? -divisor : divisor;
int result = div(dividend, divisor);
return sign == 1 ? result : -result;
}
private int div(int dividend, int divisor) {
// 如果被除数比除数大, 直接返回0
// 注意这里都是负数, 所以比较的是绝对值
if (dividend > divisor) {
return 0;
}
// 被除数比除数小(绝对值大), 则被除数至少有1个除数(数值比较)
// count记录被除数的数值等于几个除数
int count = 1;
// 既然count至少为1, 那就尝试让除数翻倍, 看看count能否达到2
int curDivisor = divisor;
while (dividend < curDivisor + curDivisor && curDivisor + curDivisor < 0) {
count += count; // 结果翻倍
curDivisor += curDivisor; // 被除数翻倍
}
// 还需要检查dividend减去curDivisor后是否还能包括几个divisor
return count + div(dividend - curDivisor, divisor);
}
}