leetcode力扣第29题:两数相除

这题看似简单,实则一点也不难(不是),实则还是比较困难。

最简单的做法是直接用减法,不停循环计数,最后统计减多少次能成。

如果被除数是2^31-1或差不多大小的数,而除数是1差不多大小的数,那循环减法要执行的次数太多,一定会超时。

所以一定要有更好的思路

(1)通过二分法查找可能的商

(2)对于每一个可能得商,跟除数相乘,然后跟被除数比大小,根据大小关系来决定商应该去左边区间还是右边区间。这里不能直接用乘法,所以得用类似于二进制竖式的方式,快速通过加法结合位移运算实现"快速乘法"

还有一些细节上的难点,代码注释中写得很清楚。

java 复制代码
class Solution {
    public int divide(int dividend, int divisor) {
        // 考虑被除数为最小值的情况
        if (dividend == Integer.MIN_VALUE) {
            if (divisor == 1) {
                return Integer.MIN_VALUE;
            }
            if (divisor == -1) {
                return Integer.MAX_VALUE;
            }
        }
        // 考虑除数为最小值的情况
        if (divisor == Integer.MIN_VALUE) {
            return dividend == Integer.MIN_VALUE ? 1 : 0;
        }
        // 考虑被除数为 0 的情况
        if (dividend == 0) {
            return 0;
        }

        // 一般情况,使用二分查找
        // 将所有的正数取相反数,这样就只需要考虑一种情况
        boolean rev = false;
        if (dividend > 0) {
            dividend = -dividend;
            rev = !rev;
        }
        if (divisor > 0) {
            divisor = -divisor;
            rev = !rev;
        }

        //divisor绝对值至少也是1,因此商不可能比dividend的绝对值大,因此right初始值设为-dividend即可
        //(大部分情况下-dividend要比Integer.MAX_VALUE小,因此能节约时间)
        //但是dividend有可能为Integer.MIN_VALUE,这时right设为
        //int left = 1, right = Integer.MAX_VALUE, ans = 0;
        int left = 1, right = dividend==Integer.MIN_VALUE?Integer.MAX_VALUE:-dividend, ans = 0;
        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            //true表示divisor * mid >= dividend,此时应该增大mid试试,
            // 注意dividend和divisor为负值,mid为正值,注意大小关系关系不用弄反了
            // 注意 divisor * mid = dividend的情况这里也是增大了mid,后续范围进一步缩小,到left = right时一定可以把商找出来
            //false表示表示divisor * mid < dividend,此时应该减少mid试试
            boolean b = quickAdd(divisor, mid, dividend);
            if (b) {
                //这里为什么是b==true才有ans = mid?以及上面为什么left<=right 而非 left < right?确实很难理解
                // 7/(-3) 得-2 这里会出现left = right = 3的情况,但2才是正确答案
                /**
                 * 还是以7/(-3)举例
                 * right会不断缩小,直至有一次循环进来left=1,right=3 => mid = 2
                 * 算出来divisor * mid >= dividend (-6 >= -7)
                 * 此时-2已经是正确答案,并且已赋值给ans
                 *
                 * 下一轮循环 left = 3 ,right = 3 这里就说明while的条件应该是left <= right而非left < right
                 * mid = 3.进入b = false的循环
                 *
                 * 下一次到while语句时
                 * right=2 left = 3 循环跳出
                 *
                 * 这里还有一个关键,因为b==true表示divisor * mid >= dividend,其中包括了divisor * mid == dividend
                 * 的情况,如果divisor * mid == dividend,那mid当然是正确答案。
                 * 如果divisor * mid < dividend,比如-3 * 2 < -7,这里mid也可能是正确答案
                 * 但如果divisor * mid > dividend,那么mid一定不是正确答案,
                 * 所以 ans = mid 一定在divisor * mid >= dividend的分支
                 */
                ans = mid;
                if (mid == Integer.MAX_VALUE) {
                    break;
                }
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }

        return rev ? -ans : ans;
    }


    /**
     * 快速乘
     * 注意: divisor<0,mid>0,dividend<0 累加的过程是往x轴负方向靠近的过程
     * 累加的结果应该比dividend大(绝对值比dividend的小),一旦比dividend小了,说明这个mid肯定偏大了,应返回false
     * 同时为了累加结果朝过int的边界,判断条件应注意写法
     * 相当于二进制的竖式运算!!
     */

    public boolean quickAdd(int divisor, int mid, int dividend) {
        int result = 0;
        int add = divisor;
        while (mid != 0) {
            //当前mid最后一位为1
            if ((mid & 1) == 1) {
                if (result < dividend - add) {
                    return false;
                }
                result += add;
            }

            // 注意,add变为原来的两倍,相当于算数左移一位,也可能超过dividend的界限,甚至超过int的界限
            // 我们保证其不超过dividend的界限,当然也就避免了超过int的界限
            // 如果add变为原来的两倍后超过了dividend的界限,因为mid != 1,说明高位肯定有至少1个1,那加上后肯定越界了
            if (mid != 1) {
                if (add < dividend - add) {
                    return false;
                }

                
                //add += add; //这里不能用add = add << 1,因为add是负数,左移会丢失符号位
                //上面的说法是错误,实际上并不会
                add = add << 1;
            }

            mid = mid >> 1; // 等价于 mid >>= 1
        }

        //走到这里,说明最后的result >= dividend,也就是说绝对值比dividend小
        return true;
    }
}
相关推荐
酷酷的崽7981 小时前
【数据结构】——原来排序算法搞懂这些就行,轻松拿捏
数据结构·算法·排序算法
八月的雨季 最後的冰吻2 小时前
C--字符串函数处理总结
c语言·前端·算法
阿拉伯的劳伦斯2923 小时前
LeetCode第一题(梦开始的地方)
数据结构·算法·leetcode
Mr_Xuhhh3 小时前
C语言深度剖析--不定期更新的第六弹
c语言·开发语言·数据结构·算法
吵闹的人群保持笑容多冷静4 小时前
2024CCPC网络预选赛 I. 找行李 【DP】
算法
桃酥4034 小时前
算法day22|组合总和 (含剪枝)、40.组合总和II、131.分割回文串
数据结构·c++·算法·leetcode·剪枝
山脚ice4 小时前
【Hot100】LeetCode—55. 跳跃游戏
算法·leetcode
桃酥4034 小时前
算法day21|回溯理论基础、77. 组合(剪枝)、216.组合总和III、17.电话号码的字母组合
java·数据结构·c++·算法·leetcode·剪枝
小丁爱养花4 小时前
DFS算法专题(一)——二叉树中的深搜【回溯与剪枝的初步注入】
java·开发语言·算法·leetcode·深度优先·剪枝
潮汐退涨月冷风霜5 小时前
决策树模型python代码实现
python·算法·决策树