力扣日记剑指 Offer II 001

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. 后记

仅分享自己的想法,有意见和指点非常感谢

猜你喜欢

转载自blog.csdn.net/qq_51204877/article/details/131260319
今日推荐