由于题目规定,我们不能用64位整数,所以我们要在没有乘法和除法的情况下,还有注意不能溢出。
这个对初学者来说,还是有点难度的,我现在对如何解决溢出,没有经验,或者说第一次看到这个题目,一脸懵,我的想法很简单,就是一直减法,直到小于0,这个想法很暴力,但是不行。
所以我只能求助于我的官方,他首先针对特殊情况,进行处理。
这里还是能理解的。
后面就是说做除法一般要分四类,但是不要这么麻烦,做几个分支,把两个数都转为负数,这样只需要考虑一种溢出。就是2的31次方.
if (dividend == INT_MIN) {
if (divisor == 1) {
return INT_MIN;
}
if (divisor == -1) {
return INT_MAX;
}
}
// 考虑除数为最小值的情况
if (divisor == INT_MIN) {
return dividend == INT_MIN ? 1 : 0;
}
// 考虑被除数为 0 的情况
if (dividend == 0) {
return 0;
}
之后官方提出是一个二分查找的方法。原位两个数都是负数,x/y=z,z的结果经过舍入,整数除法结果还是整数,应该很明白的,所以z<=x/y<z+1,同时y<0,变换一下,z*y>=x>(z+1)*y.所以后面的任务就是在z的取值范围内二分查找,获得最大的z使不等式成立.因为x/y>=z,所以z有一个最大值整数
最开始是确定了z得符号,同时将两个数变为负数,rev是false就是两个符号一样,z>0,同理,rev是true,就说明只有一个正得,所以z<0.这个是最后加上得符号
bool rev = false;
if (dividend > 0) {
dividend = -dividend;
rev = !rev;
}
if (divisor > 0) {
divisor = -divisor;
rev = !rev;
}
官方为了计算z*y>=x,用了一个快速乘的方法,因为不能用加法,这个方法是将z变成二进制,然后累加的方法,比如z是3,二进制是11,而y=-5,x-11,result=0,add=y然后z是11,最低位是1,所以result=0+add=-5,之后z右移一位(z>>1),现在这个循环里面取得是倒数第二位,也是1,然后将add翻倍,add=-10,在将result+add=-5+-10=-15.就等于z*y.官方就是在每次检查result+add,或者add+add,来确定z*y>=x,同时用减法,也防止溢出,这个就是比较核心得一点.快速乘方法,确定z*y>=x.这个之后是二分查找得条件.
auto quickAdd = [](int y, int z, int x) {
// x 和 y 是负数,z 是正数
// 需要判断 z * y >= x 是否成立
int result = 0, add = y;
while (z) {
if (z & 1) {
// 需要保证 result + add >= x
if (result < x - add) {
return false;
}
result += add;
}
if (z != 1) {
// 需要保证 add + add >= x
if (add < x - add) {
return false;
}
add += add;
}
// 不能使用除法
z >>= 1;
}
return true;
};
后面就要开始二分查找了,
-
初始化变量:
int left = 1, right = INT_MAX
:设置二分查找的边界,left
为1,right
为整数的最大值。int ans = 0
:用于记录最大的有效乘数。
-
二分查找:
while (left <= right)
:当left
小于等于right
时,继续查找。
-
计算中值
mid
防溢出:int mid = left + ((right - left) >> 1)
:使用这种方式来计算中值可以防止left + right
直接相加时可能的整数溢出。
-
使用
quickAdd
函数检查:bool check = quickAdd(divisor, mid, dividend)
:调用quickAdd
来检查divisor * mid
是否小于等于dividend
。
-
更新二分查找的边界:
- 如果
check
为真,则ans = mid
,并且尝试通过将left
设置为mid + 1
来找更大的值。 - 注意,如果
mid
已经是INT_MAX
,则直接跳出循环以避免无限循环或溢出。 - 如果
check
为假,则将right
设置为mid - 1
,寻找更小的乘数。
- 如果
-
返回结果:
- 最后,根据
rev
的值(未在此段代码中定义,但通常用于处理负值的情况),返回ans
的正负正确值。如果rev
是真,则返回-ans
;否则返回ans
- 最后,根据
int left = 1, right = INT_MAX, ans = 0;
while (left <= right) {
// 注意溢出,并且不能使用除法
int mid = left + ((right - left) >> 1);
bool check = quickAdd(divisor, mid, dividend);
if (check) {
ans = mid;
// 注意溢出
if (mid == INT_MAX) {
break;
}
left = mid + 1;
}
else {
right = mid - 1;
}
}
return rev ? -ans : ans;
}
};
最后我把官方得整个代码放上来
class Solution {
public:
int divide(int dividend, int divisor) {
// 考虑被除数为最小值的情况
if (dividend == INT_MIN) {
if (divisor == 1) {
return INT_MIN;
}
if (divisor == -1) {
return INT_MAX;
}
}
// 考虑除数为最小值的情况
if (divisor == INT_MIN) {
return dividend == INT_MIN ? 1 : 0;
}
// 考虑被除数为 0 的情况
if (dividend == 0) {
return 0;
}
// 一般情况,使用二分查找
// 将所有的正数取相反数,这样就只需要考虑一种情况
bool rev = false;
if (dividend > 0) {
dividend = -dividend;
rev = !rev;
}
if (divisor > 0) {
divisor = -divisor;
rev = !rev;
}
// 快速乘
auto quickAdd = [](int y, int z, int x) {
// x 和 y 是负数,z 是正数
// 需要判断 z * y >= x 是否成立
int result = 0, add = y;
while (z) {
if (z & 1) {
// 需要保证 result + add >= x
if (result < x - add) {
return false;
}
result += add;
}
if (z != 1) {
// 需要保证 add + add >= x
if (add < x - add) {
return false;
}
add += add;
}
// 不能使用除法
z >>= 1;
}
return true;
};
int left = 1, right = INT_MAX, ans = 0;
while (left <= right) {
// 注意溢出,并且不能使用除法
int mid = left + ((right - left) >> 1);
bool check = quickAdd(divisor, mid, dividend);
if (check) {
ans = mid;
// 注意溢出
if (mid == INT_MAX) {
break;
}
left = mid + 1;
}
else {
right = mid - 1;
}
}
return rev ? -ans : ans;
}
};
作者:力扣官方题解
链接:https://leetcode.cn/problems/divide-two-integers/solutions/1041939/liang-shu-xiang-chu-by-leetcode-solution-5hic/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。