天天AC系列(十三):两数相除

1 题目

LeetCode第29题,计算两数相除的商,不容许使用乘法,除法,求模运算符。
在这里插入图片描述java

2 减法

首先判断结果是否须要加上负号,将商置为0后,被除数不断减去除数,同时商自增。最后根据是否有负号返回相应的商。git

boolean negative = true;
if((dividend > 0 && divisor > 0) || (dividend < 0 && divisor < 0))
    negative = false;
dividend = dividend > 0 ? dividend : -dividend;
divisor = divisor > 0 ? divisor : -divisor;
int result = 0;
while(dividend >= divisor)
{
    dividend -= divisor;
    ++result;
}
return negative ? -result : result;

3 思考

3.1 溢出

在这里插入图片描述
-2147483648与2147483647这两个数,是4字节的int的最小值与最大值,在java中,它们可用Integer.MIN_VALUE与Integer.MAX_VALUE表示,当一个int为Integer.MIN_VALUE时,取反也是这个数:
在这里插入图片描述
在这里插入图片描述
最简单最粗暴的解决方案就是使用long,long能够放下-Integer.MIN_VALUE,所以,直接将被除数与除数的类型改为long,返回值转为long便可。
可是提交了一下,超时:
在这里插入图片描述
因此对除数1与-1进行特判一下:程序员

if(divisor == 1)
    return dividend;
if(divisor == -1)
{
    if(dividend == Integer.MIN_VALUE) return Integer.MAX_VALUE;
    return -dividend;
}

若除数是1直接返回被除数,这时不须要考虑溢出,如果除数是-1,须要特判一下被除数是否为int的最小值,由于-Intger.MIN_VALUE也是Intger.MIN_VALUE,题目也说了返回int的最大值。
而后信心十足地提交了:
在这里插入图片描述
惨淡。github

3.2 负数

溢出的缘由,就算由于负数的存储范围比正数多1,就算由于那两个可恶的-2147483648与2147483647.
上面的作法是判断结果的负号,而后将被除数与除数都转为正数来计算,能够换一种思路,将被除数与除数都转为负数来计算:ide

dividend = dividend > 0 ? -dividend : dividend;
divisor = divisor > 0 ? -divisor : divisor;
int result = 0;
while(dividend <= divisor)
{
    dividend -= divisor;
    ++result;
}
if(negative)
{
    if(-result == Integer.MIN_VALUE)
        return Integer.MIN_VALUE;
    return -result;
}
else
{
    if(result == Integer.MIN_VALUE)
        return Integer.MAX_VALUE;
    return result;
}

结果从0开始自增,while循环的条件改为被除数小于等于除数而不是以前的被除数大于等于除数,而后对得出的商判断正负与边界,若是是负数,判断商的相反数是不是int的最小值,如果的话,表示真正的商为2147483648,负溢出,返回int的最小值,若不是负数,判断商是否为int的最小值,如果的话,表示真正的商为2147483648,正溢出,返回int的最大值。
在这里插入图片描述
快了600ms,仍是有效果的。code

3.3 翻倍与移位

速度慢的缘由,是由于减法。所以须要改进减法,使被除数更快地逼近除数。
对于被除数为2147483647,除数为1的状况,须要减2147483647次,才能得出结果,因此,使用翻倍,第一次减1,第二次减2,第三次减4,以此类推。
可是怎么翻倍怎么操做呢?blog

a *= 2 ?

题目说不能用乘法运算符。
做为一个现代的程序员,总不能这样翻倍吧?递归

a += a;

这时就轮到位移运算符登场了,左移一位,至关于乘2,右移一位至关与除2:图片

a <<= 1; // a *= 2
a >>= 1; // a /= 2

整体思路是设置一个tempResult与一个tempDivisor,不断将tempResult与tempDivisor翻倍,直到被除数大于等于tempDivisor或tempDivisor溢出,而后把tempResult增长到result上面。leetcode

while(dividend <= divisor)
{
    int tempDivisor = divisor;
    int tempResult = 1;
    while(dividend < (tempDivisor<<1) && tempDivisor > (Integer.MIN_VALUE >> 1))
    {
        tempDivisor <<= 1;
        tempResult <<= 1;
    }
    dividend -= tempDivisor;
    result += tempResult;
}

其中:

tempDivisor > (Integer.MIN_VALUE >> 1)

这个while中的判断很重要,若是tempDivisor大于int的最小值的一半,则tempDivisor左移1位后会小于Integer.MIN_VALUE,也就是小于int的最小值,会溢出,跳出循环后会致使被除数减去一个正数而不是一个负数,这样至关于增大了被除数致使计算的结果错误。
在这里插入图片描述

4 递归

递归能够减小设置一个result变量,直接在返回值里加上便可:

public int div(int dividend,int divisor)
{
    if(dividend <= divisor)
    {
        int tempDivisor = divisor;
        int tempResult = 1;
        while(dividend < (tempDivisor<<1) && tempDivisor > (Integer.MIN_VALUE >> 1))
        {
            tempDivisor <<= 1;
            tempResult <<= 1;
        }
        return tempResult + div(dividend-tempDivisor,divisor);
    }
    return 0;
}

代码与迭代基本相同,结束条件为被除数大于除数,在进入递归前须要对被除数与除数处理正负:

public int divide(int dividend,int divisor)
{
    boolean negative = (dividend > 0) ^ (divisor > 0);
    int result = div(dividend > 0 ? -dividend : dividend,divisor > 0 ? -divisor : divisor);
    if(negative) return -result;
    return result == Integer.MIN_VALUE ? Integer.MAX_VALUE : result;
}

在这里插入图片描述

5 源码

github

码云

相关文章
相关标签/搜索