1. 题目
LeetCode 剑指 Offer II 001. 整数除法
1.1 题意
模拟除法,不使用乘除号
1.2 分析
首先模拟除法可以使用减法,通过不断减去除数,得到结果。这样的时间复杂度最坏情况下是(2^31)(即INT_MIN / 1),肯定过不了(大概在1e6左右才能过oj)。
如果知道快速幂的话,可以联想到同过类似快速幂的方法,求出b, 2b, 4b, 8b…… 然后从中还可以通过二分的方式来查找每次可以减去的除数倍数。
-2^31 <= a, b <= 2^31 - 1
b != 0
然后说一下细节,因为可能涉及到a,b异号情况,所以要考虑将a,b变为同号(不然每次减去的除数的倍数后符号会有问题,可以自己简单举个例子)
一开始我将两个数都变成了正号,结果出现问题?为什么,因为int补码范围负数比正数多1,取负会溢出。开始我使用long来存,但是实际已经不满足题目要求了。然后想到只有异号情况需要特殊处理,如果两个数都转换成负数,结果还是相同(因为 ( − a ) / ( − b ) = a / b (-a)/(-b)=a/b (−a)/(−b)=a/b,而且不会有溢出的情况。
另外 题目中的溢出的情况,只有INT_MIN/(-1) 才会溢出,需要特判。
对于二分的细节写在代码注释中。
1.3 我的解法
class Solution {
public:
int divide(int a, int b) {
// 结果是否需要加负号
int negFlag = 0;
if(a==INT_MIN && b==-1)
// 特判溢出的情况
return INT_MAX;
if(a>0){
// 将被除数变成负数
a = -a;
// 使用异或记录下符号变化的次数
// 奇数次则说明结果需要添加负号
negFlag ^= 1;
}
if(b>0){
// 被除数变成负数
b = -b;
negFlag ^= 1;
}
// dichotomy
// b 2b 4b 8b 16b 32b
// 利用二分法(类似快速幂)
// 一个记录下除数的倍数 b 2b 4b 8b .....
// 一个记录下对应的系数 1 2 4 8 ......
// 为什么要记系数?
// 因为不能使用乘法,根据下标难以快速得到系数
vector<long> mult; // store b 2b ......
vector<long> base; // store 1 2 4 8 ......
mult.emplace_back(b);
base.emplace_back(1);
// generate mult and base
// 生成除数的倍数
// 直到除数的倍数超过被除数a
while(mult.back()>=a){
mult.emplace_back(mult.back() + mult.back() );
base.emplace_back(base.back() + base.back() );
}
// compute ans
long res = 0;
int n = mult.size();
// 要注意这里头mult中的 b 2b 4b ....是降序排列
// 因为b是负数
while(a <= mult[0]){
// dichotomy
// find ind: mult[ind] < a && mult[ind-1] >= a
// 找到第一个比剩余的a小的数
int l=0, r=n-1, mid = (l+r)/2;
while(l<r){
// 细节:怎么判断是否这两个边界会造成死循环
// 死循环的根本是最后 l r相近的时候,mid不会继续改变
// 举例 比如 l=0, r=1, mid= 0,然后再分支中l=mid,这类情况
// 关键在于不能缩小范围的那个分支里头,比如下面是r=mid
// 可以举具体例子代入,康康是否会出现死循环,
// 在下面代码情况中是不会的l=0, r=1, mid= 0,只有可能r=0,或者l=1都能缩小范围到退出循环
if(mult[mid] < a){
r = mid;
}
else{
l = mid + 1;
}
mid = (l+r) / 2;
}
res += base[mid-1];
a -= mult[mid-1];
}
// 注意符号
if(negFlag)
return int(-res);
return res;
}
};
1.4 学习题解反思
我的解法:
时间复杂度O( ( l o g ( a / b ) ) 2 (log (a/b))^2 (log(a/b))2 ),
在计算除数的倍数时最多需要log(a/b) + 1次
在计算除法结果时,最多需要log(a/b) + 1循环,在循环里头每次都需要找到可以减去的除数的倍数,使用二分也是log(a/b) + 1次查找,
和一块是O( ( l o g ( a / b ) ) 2 (log (a/b))^2 (log(a/b))2 )
空间复杂度O(log(a/b)) ,需要存除数的倍数和对应的系数
学习题解:
题解中对求解目标进行变换,改求Z×Y≥X>(Z+1)×Y, 同样使用二分求Z,这使得题解中的空间复杂度变成O(1),妙啊。另外强调了细节(Z+1)×Y可能会溢出,需要再次变形(我来写的话必漏这个细节)
题解中的类二分查找看的不是很明白,感觉有点类似我的写法。
另外,我发现题解中没存系数,因为直接使用位运算1<<i 可以表示出下标i对应的系数,fine,学到了again.
1.5 bug日记
1.5.1 取负溢出情况
1.5.2 除法结果溢出
2. 后记
仅分享自己的想法,有意见和指点非常感谢