栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

整数除法 | 循序递进---@二十一画

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

整数除法 | 循序递进---@二十一画

整数除法
      • 题目:
      • 分析:
        • 方向:
        • 额外:
        • 优化:【脱离细节看整体,越看越混:脱离整体看细节,越看越杂】
      • 代码:
        • 第一版:
        • 第二版:
        • 第三版:
      • 总结:

题目:

给定两个整数 a 和 b ,求它们的除法的商 a/b ,要求不得使用乘号 '*'、除号 '/' 以及求余符号 '%' 。

注意:

整数除法的结果应当截去(truncate)其小数部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2
假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−2^31, 2^31−1]。本题中,如果除法结果溢出,则返回 2^31 − 1

提示:

-2^31 <= a, b <= 2^31 - 1
b != 0

示例 1:

输入:a = 15, b = 2
输出:7
解释:15/2 = truncate(7.5) = 7

示例 2:

输入:a = 7, b = -3
输出:-2
解释:7/-3 = truncate(-2.33333..) = -2

示例 3:

输入:a = 0, b = 1
输出:0

示例 4:

输入:a = 1, b = 1
输出:1
分析:

题目中给定如下需求:

  1. 不得使用 * / % 等计算符号
  2. 结果取整数部分,且包含一般的正负符号
  3. 当结果溢出,返回MAX_VALUE
方向:

无法使用乘除等计算方式,可以想到乘除的本质在计算器内部就是加法(减法)实现的。故这里可以采取循环x=被除数-除数。【这里需要特殊注意,做减法的时候,需要被减数和减数处于同一正负性】后继续执行该逻辑,直到被除数不大于除数

额外:
  1. 必然越界问题:当传入一些特定值时,结果必然溢出,此处可以直接在函数开始位置定义逻辑。如当a=-2^31,b=1,理想结果是2^31,但是在int中已经溢出。
  2. 非必然越界问题:在计算过程中或者逻辑执行中出现越界问题,此处需要特殊处理。如当处理减数和被减数为同一正负性的时候,如果存在数字为-2^31,那么转化为正数就会越界,而对应的最大正数转化为负数却不会越界,所这里可以将所给数字都化为负数来避免非必然越界问题。
优化:【脱离细节看整体,越看越混:脱离整体看细节,越看越杂】
  1. 虽然将优化写在了这个位置,但是本人还是不建议在开始阶段【第一版代码未通过】就着想优化逻辑。
  2. 使用减法方式需要被减数和减数循环进行相减,如果两者差距很大,可能需要执行n次。所以优化方向就在于减少相减次数,提高代码效率。
代码: 第一版:

描述:只实现减法过程,不关心执行次数【先实现功能,再优化细节】

class Solution {
    public int divide(int a, int b) {

        int max = Integer.MAX_VALUE; //2^31-1   2147483647
        int min = Integer.MIN_VALUE; //-2^31   -2147483648

        //01、处理必然溢出 (a=min & b=-1) 
        if(a==min && b==-1) return max;

        //02、记录符合,除法结果需要含正负性,正负性即是传入a,b自带的属性  
        //异或计算:同为false,不同为true。 如果a,b正负性不同那么flag=false
        boolean flag = (a>0)^(b>0)?false:true;

        //03、a,b 相减前正负性需保持一致
        //04、防止a,b中取值为min,转为正数溢出,故全部转移为负数
        //05、定义循环次数 count 作为返回结果
        a = a>0?-a:a;
        b = b>0?-b:b;
        int count = 0;

        //06、a,b都已是负数,负数的减法如下:a-b实则是 负数的a 加 正数的b得到新的结果。所以循环过程中需要a大于b
        //在负数之中,一个值越大,那么绝对值越小。实现除法底层是是绝对值的计算 ,所以只有绝对值a大于b,那么才有计算的意义,否则停止循环
        while(a<=b){
            a-=b;
            count++;
        }

        //07、返回结果,但是需要结合flag,返回对应正负性结果
        return flag?count:-count;
    }
}
第二版:

此版是二十一画通过阅读@老汤 该大佬的解题思路得出的改良版,大致上是按照该大佬的思路进行解题。第一版解法是被减数(a)每次循环中减一个减数(b),这种方式执行次数可以得出,process_Count<=a/b+1,此处可以降低执行次数,每次循环中可以减两个减数(b),虽然第二版提高效率并不高效,但是也提供了一种思路。

class Solution {
    public int divide(int a, int b) {

        int max = Integer.MAX_VALUE; //2^31-1   2147483647
        int min = Integer.MIN_VALUE; //-2^31   -2147483648

        //01、处理必然溢出 (a=min & b=-1) 
        if(a==min && b==-1) return max;

        //02、记录符合,除法结果需要含正负性,正负性即是传入a,b自带的属性  
        //异或计算:同为false,不同为true。 如果a,b正负性不同那么flag=false
        boolean flag = (a>0)^(b>0)?false:true;

        //03、a,b 相减前正负性需保持一致
        //04、防止a,b中取值为min,转为正数溢出,故全部转移为负数
        //05、定义循环次数 count 作为返回结果
        a = a>0?-a:a;
        b = b>0?-b:b;
        int count = 0;

        //06、第一版解法是被减数(a)每次循环中减一个减数(b),这种方式执行次数可以得出,process_Count<=a/b+1
        // 此处可以降低执行次数,每次循环中可以减两个减数(b),虽然第二版提高效率并不高效,但是也提供了一种思路
        while(a<=b){

            //07、如果|a|>=2|b|,那么可以一次性减两个减数(b) ,此处b+b可能溢出,所以需要加入判断逻辑(其实这里有点是取巧,直接使用进制位来判断溢出)
            // 0xc0000000 是 -2^31的一半,这里如果不喜欢用进制,可以换成【if( b>=min-b && a<=(b+b))】
            // 需要写成b>=min-b ,如果写成b+b=0xc0000000 && a<=(b+b)){
                a-=(b+b);
                count+=2;
            }else{
                // 第一版逻辑
                a-=b;
                count+=1;
            }
        }
        //08、返回结果,但是需要结合flag,返回对应正负性结果
        return flag?count:-count;
    }
}
第三版:

此版同样是从@老汤 大佬题解中得出的思路,一版和二版的执行次数都是由被减数和减数之前的差距来决定,所以时间复杂度不稳定,也存在变数。此版本是解决时间复杂度的解法,此处设x,y:x是被减数,y是减数,第一版和第二版的底层思想就是从x中一个一个减去y,或者是减去两个y。

那么第三版的实现,是按照 2^i 从x中减去2^i个y 【已知int类型,0<=i<=31】

所以需要遍历32次即可实现整数相除。具体逻辑空说不太清晰,我会将逻辑拆分揉碎体现在实现代码中。

class Solution {
    public int divide(int a, int b) {

        int max = Integer.MAX_VALUE; //2^31-1   2147483647
        int min = Integer.MIN_VALUE; //-2^31   -2147483648

        //01、处理必然溢出 (a=min & b=-1) 
        if(a==min && b==-1) return max;

        //02、记录符合,除法结果需要含正负性,正负性即是传入a,b自带的属性  
        //异或计算:同为false,不同为true。 如果a,b正负性不同那么flag=false
        boolean flag = (a>0)^(b>0)?false:true;

        //03、a,b 相减前正负性需保持一致
        //04、新的版本将符号保持为整数,下面会体现为何要保存为正数
        //05、定义count变量 作为结果存储
        //06、执行abs函数后,会有几个特殊情况。 当a或者b = min的时候,abs出来的结果也是min,数组是个圈 ,溢出之后系统返回的值还是自身
        // min = abs(min)   max = abs(max)  樂
        //07、想着重处理下当b=min的情况,如果b=min&&a<>min,恒等式|a|<|b|那么结果必然是0
        // 如果b==min&&a==b 那么结果必然是1,在这里将特殊逻辑处理,下面循环逻辑就更容易理解
        // 这样就处理了 b为负数的情况
        if(b==min && a!=min) return 0;
        if(b==min && a==b) return 1;

        a = Math.abs(a);  // a正负性 不确定
        b = Math.abs(b);  // b>=0 恒成立
        int count = 0;

        //05、循环逻辑
        for(int i=31;i>=0;i--){
            // 这里循环32次,因为默认存储是2进制的,a的显示可能是 【10100100 10101010 10010100 10110100】 i代表二进制的位数
            // 其实这里循环就是在找因数,已知积=a,已知一个因数为b,那么求另外一个因数(这里暂且认为因数,因为可能不是整除的,所以先把商指代为因数)。我们无法使用除法,所以无法直接得出因数,只能通过从a本身的最大因数开始寻找正确的因数。
            // 因数: 2^31 2^30 2^29 2^28 ... 2^2 2^1 2^0  
            // 举例: x * b <= a ,(x∈【2^31 2^30 2^29 2^28 ... 2^2 2^1 2^0】)
            // 当 x 太大了,我们就缩小x,直到 符合条件,后将 a=a-xb,继续循环
            // 因为 a-xb之后,在该二进制i位不可能继续为1,因为 x在二进制中代表第i位=1,减去之后该位肯定变成0了。所以可以继续循环下一位,也就是i--
            if((a>>>i)-b>=0){ 
                //这里(a>>>i)-b>=0 不可以写成 (a>>>i)>= b,如果按照后者写法,当a此次循环为min的时候,会导致min绝对值虽然大于b,但是因为min>=b不成立而产生错误逻辑
                // 正确写法应该是将判断式放在左边,如果a=min,那么a-b 会溢出,溢出成一个正数,此时符合逻辑,a的绝对值确实比b的大,所以应该走循环内的代码。 写左边没有任何影响,如果a>>i 一定是大于0的正数,同时b也大于0,所以a和b的运算是可以的。
                // ①都为正数  ②符合减数逻辑
                a-=(b< 
总结: 

个人觉得第一版和第二版比较简单,第三版位运算确实比较复杂。二十一画应该也只能想到前两种,第三种觉得可以作为了解一下。知道有这么个解法就可以。

另外其实,这道题的关键是在于,减法需要保持符号一致,如果都为正数,那么min有越界的可能,需要单独处理,如果都为负数,那么正数转负数不存在越界的情况,如此书写逻辑便简单多了。

大家好,我是二十一画,感谢您的品读,如有帮助,不胜荣幸~

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/649595.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号