leetcode29(c++):两数相除

题目:

给定两个整数,两数相除但不能用乘法、除法和mod运算符。
且:

  1. 被除数和除数均为 32 位有符号整数。
  2. 除数不为 0。
  3. 假设我们的环境只能存储 32 位有符号整数,其数值范围是 [ − 2 31 -2^{31} 231, 2 31 2^{31} 231 − 1]。本题中,如果除法结果溢出,则返回 2 31 2^{31} 231 − 1。

思路

简单来说,这题就是要求被除数中能分出多少个除数出来

那么这道题难在哪里?

  1. 存储变量只能是int类型,不能是long等类型;
  2. 被除数和除数均有可能出现负数;
  3. 要考虑溢出的情况;
  4. 当被除数很大、除数很小时,要怎么算才能更快一些?

让我们逐一击破。

针对第一点,因为只能存储int类型,范围与除数与被除数的范围是一致的,所以这里我们等下使用的方法是将除法转成减法,比如说被除数为50,除数为3,则50减18次3等于2<3,则最后为18。

针对第二点,我们理所当然地会考虑到:我把它们使用abs【绝对值】全都转成正数或者负数处理不就好了!但是别忘了,当除数或者被除数等于INT_MIN(- 2 31 2^{31} 231)时,转成正数会溢出,这就很难受了。这正是第三点要考虑的问题。

针对第三点,当被除数等于INT_MIN时,我们无法直接对它取绝对值,自然就想到,能不能先减少一点数值再取绝对值呢?答案是可以的,我们可以先减去一个除数,再对其取绝对值,然后把result的值等于1(分出一个除数出来)。

针对第四点,我们可以由除数减去【被除数的两倍】,再两倍两倍地增加(判断是否能超过除数的4倍、8倍……),直到被除数干不过(小于)上一时刻的两倍,那么就此作罢,退出小循环,结算一次,让剩余的被除数=被除数-可以干得过的除数的倍数,同时让result也相应的增加(干了多少倍就加多少),然后用剩余的被除数【重征沙场】,重新去干除数的倍数,当然除数的倍数要从头开始加(2倍、4倍、8倍……),以此类推。这样可以达到快速的效果,那么问题来了,不是说不能用乘法吗?!你这里怎么用了!这是个好问题,我们可以使用左移右移来达到乘法除法的效果。

公式如下:

d i v i d e n d = . . . + 0 ∗ d i v i s o r ∗ 2 2 + 1 ∗ d i v i s o r ∗ 2 1 + 1 ∗ d i v i s o r ∗ 2 0 dividend = ... + 0*divisor* 2^2 + 1*divisor*2^1 + 1*divisor*2^0 dividend=...+0divisor22+1divisor21+1divisor20
a n s = . . . + 0 ∗ 2 2 + 1 ∗ 2 1 + 1 ∗ 2 0 ans = ... + 0*2^2 + 1*2^1 + 1*2^0 ans=...+022+121+120

编程

一、首先要考虑一些特殊的情况:

  1. 被除数等于除数时,返回1;
  2. 除数为1时,返回被除数;
  3. 被除数等于最小的负数INI_MIN,除数等于-1时,会溢出,返回INT_MAX;
  4. 除数为INI_MIN时,若被除数为INI_MIN,则返回1,其余为0。
int INI_MIN = -2147483648,INI_MAX =2147483647;
if(dividend == divisor) return 1; 
if(divisor == 1) return dividend;
if(dividend == INI_MIN && divisor == -1) return INI_MAX;
if(divisor == INI_MIN) { if(dividend == INI_MIN) return 1; else return 0;}

二、为一正一负的情况设定标志,方便后续统一处理(针对难点2)

bool sign = false;
  if(dividend > 0 && divisor > 0 || dividend < 0 && divisor < 0 )
  {
      sign =true;
  }

这种方法虽然笨但非常实用(起码不用考虑溢出的问题),如果使用if(a*b>0)来判断是一正一负情况,数字太大会溢出。

三、当被除数为INI_MIN时,无法取绝对值转成正数处理,故让它先减去一个除数。(针对难点3)

注意这里也要分除数是正还是负,正的话就是加,负就减,操作完还要让减除数的次数+1。

int result_1 = 0;	
  if(dividend == INI_MIN)
  {
      if(divisor > 0)
        dividend = dividend + divisor;
      else dividend = dividend - divisor;
      result_1++;
  }

四、这时候除数和被除数都不会溢出了,我们就可以放心地取它们的绝对值了。

 dividend = abs(dividend);
 divisor = abs(divisor);

五、快速地运算(针对难点4)
其中,左移<<1位表示增加一倍(相当于数字×2)

    while(dividend>=divisor)//代表x里面还能分出y
    {
        unsigned int temp=divisor,res=1;
        //然后开始比较是否大于y的倍数,一次从x里面减去最大的2^n*y
        while(dividend>=(temp<<1))
        {
            res<<=1;
            temp<<=1;
        }
        //res代表temp里面有多少个y,所以在x减去temp后,res也要加在result里。
        result_1+=res;
        dividend-=temp;
    }

六、收尾

    if (sign == true) { // 正数
        return result_1;
    } else {
        return -result_1;
    }

总体代码


int divide(int dividend, int divisor)
{
  int INI_MIN = -2147483648,INI_MAX =2147483647;
  if(dividend == divisor) return 1;
  if(divisor == 1) return dividend;
  if(dividend == INI_MIN && divisor == -1) return INI_MAX;
  if(divisor == INI_MIN) { if(dividend == INI_MIN) return 1; else return 0;}

  bool sign = false;
  if(dividend > 0 && divisor > 0 || dividend < 0 && divisor < 0 )
  {
      sign =true;
  }
  //当dividend为INI_MIN时,不能直接用abs函数,先用减法,使dividend变小,然后再使用abs函数
  int result_1 = 0;
  if(dividend == INI_MIN)
  {
      if(divisor > 0)
        dividend = dividend + divisor;
      else dividend = dividend - divisor;
      result_1++;
  }
  //此时dividend、divisor都不会溢出
  dividend = abs(dividend);
  divisor = abs(divisor);
    while(dividend>=divisor)//代表x里面还能分出y
    {
        unsigned int temp=divisor,res=1;
        //然后开始比较是否大于y的倍数,一次从x里面减去最大的2^n*y
        while(dividend>=(temp<<1))
        {
            res<<=1;
            temp<<=1;
        }
        //res代表temp里面有多少个y,所以在x减去temp后,res也要加在result里。
        result_1+=res;
        dividend-=temp;
    }
    if (sign == true) { // 正数
        return result_1;
    } else {
        return -result_1;
    }
}

参考:(下面两位大佬都用了long类型,而我的没用)
https://blog.csdn.net/qq_34018840/article/details/90752719
https://zhuanlan.zhihu.com/p/105603940

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值