首页 > 其他分享 >leetcode力扣第29题:两数相除

leetcode力扣第29题:两数相除

时间:2024-08-05 23:28:14浏览次数:11  
标签:divisor ans mid 相除 力扣 VALUE Integer dividend leetcode

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

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

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

所以一定要有更好的思路

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

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

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

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;
    }
}

标签:divisor,ans,mid,相除,力扣,VALUE,Integer,dividend,leetcode
From: https://blog.csdn.net/m0_63246220/article/details/140939169

相关文章

  • LeetCode 1631. Path With Minimum Effort
    原题链接在这里:https://leetcode.com/problems/path-with-minimum-effort/description/题目:Youareahikerpreparingforanupcominghike.Youaregiven heights,a2Darrayofsize rowsxcolumns,where heights[row][col] representstheheightofcell (row,c......
  • day32【LeetCode力扣】541. 反转字符串 II
    day32【LeetCode力扣】541.反转字符串II1.题目描述给定一个字符串s和一个整数k,从字符串开头算起,每计数至2k个字符,就反转这2k字符中的前k个字符。如果剩余字符少于k个,则将剩余字符全部反转。如果剩余字符小于2k但大于或等于k个,则反转前k个字符,其余字符......
  • LeetCode面试150——13罗马数字转整数
    题目难度:简单默认优化目标:最小化平均时间复杂度。Python默认为Python3。目录1题目描述2题目解析3算法原理及代码实现3.1模拟法参考文献1题目描述罗马数字包含以下七种字符:I,V,X,L,C,D和M。字符    数值I      1V    ......
  • 【Python】Python中的输入与输出——内附Leetcode【151.反转字符串中的单词】的C语言
    输入与输出导读一、Python中的输出1.1基本用法1.2格式化输出1.3通过`:`格式化值的输出1.4其它格式化输出二、Python中的输入2.1基本用法2.2`split()`方法2.3split()习题演练结语导读大家好,很高兴又和大家见面啦!!!在上一篇内容中我们介绍了Python中的数据类......
  • leetcode200. 岛屿数量C++题解,精美图例和流程图,一题带你弄懂图的dfs遍历算法
    leetcode200.岛屿数量给你一个由‘1’(陆地)和‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。此外,你可以假设该网格的四条边均被水包围。示例1:输入:grid=[[“1”,“1”,“1”,......
  • 【动态规划】力扣918. 环形子数组的最大和
    给定一个长度为n的环形整数数组nums,返回nums的非空子数组的最大可能和。环形数组意味着数组的末端将会与开头相连呈环状。形式上,nums[i]的下一个元素是nums[(i+1)%n],nums[i]的前一个元素是nums[(i-1+n)%n]。子数组最多只能包含固定缓冲区nu......
  • [LeetCode] 2053. Kth Distinct String in an Array
    Adistinctstringisastringthatispresentonlyonceinanarray.Givenanarrayofstringsarr,andanintegerk,returnthekthdistinctstringpresentinarr.Iftherearefewerthankdistinctstrings,returnanemptystring"".Notethat......
  • LeetCode 热题 HOT 100 (017/100)【宇宙最简单版】
    【链表】No.0148排序链表【中等】......
  • LeetCode 136场双周赛 Q4. 标记所有节点需要的时间
    这道题也是一道比较经典的树形dp模板题;太久没写了,赛时一眼就想到了,但是敲的时候摸索了半天,还是没敲出来;首先看题目,需要求出无向树上从每个节点为树根节点到其他所有节点中最长路径的值,然后每条边的距离其实就是,如果目的地是奇数距离为1,目的地是偶数距离为2那么这个逻辑很简单,其......
  • Leetcode 3244. Shortest Distance After Road Addition Queries II
    Leetcode3244.ShortestDistanceAfterRoadAdditionQueriesII1.解题思路2.代码实现题目链接:3244.ShortestDistanceAfterRoadAdditionQueriesII1.解题思路这一题的话由于题目限制了road不会交叉,因此我们只需要在每次增加road之后将中间节点删除,剩余的路......