剑指Offer-面试题1:整数除法——你真的会用Math.abs吗?

1,194 阅读3分钟

整数除法

题目要求

输入2个int型整数,它们进行除法计算并返回商,要求不得使用乘号'*'、除号'/'及求余符号'%'。当发生溢出时,返回最大的整数值。假设除数不为0。例如,输入15和2,输出15/2的结果,即7。

有问题的方法

一上来就想到用减法的方法,直接一顿输出。


public class DivisionClass {
    public static Integer DivisionMethod(Integer dividend, Integer divisor) {
        Integer signNum = 0;
        Integer quotient = 0;
        if (divisor == 0) {
            throw new RuntimeException("除数不能为0");
        }
        if (dividend == 0) {
            return quotient;
        }
        if (Math.signum(dividend) == Math.signum(divisor)) {
            //同符号则signNum为正
            signNum = 1;
        } else {
            signNum = -1;
        }
        
        Integer pureDividend = Math.abs(dividend);
        Integer pureDivisor = Math.abs(divisor);
        while (pureDividend > 0) {
            pureDividend = Math.subtractExact(pureDividend, pureDivisor);
            if (pureDividend > 0) {
                quotient++;
            }
        }
        return quotient * signNum;
    }
}

很明显,问题多多!

问题一

题目中要求输入的是int类型,但是函数定义给的是输入两个Integer,这和题目中的要求不同

问题二

题目中要求不能使用乘除,但是在最后一个return quotient * signNum;这里很不幸,忘记了这一点,使用了乘法。。。

问题三

大量使用了Math类中的方法,题目中倒也没说不行,但是后来看了解法之后,感觉还是少使用Math类中的方法更好 同时,对于Math类中的方法,使用中的坑还是不太注意 注意这里的Math.abs

Note that if the argument is equal to the value of Integer.MIN_VALUE, the most negative 
representable int value, the result is that same value, which is negative.

在它的注释里面有写当输入的是Integer.MIN_VALUE的时候,返回的结果还是Integer.MIN_VALUE,这里是因为 int的最大值是2的31次方-1(0x7fffffff),int的最小值是-2的31次方(0x80000000) image.png 所以while (pureDividend > 0)这里实际上就有问题了,当输入的是Integer.MIN_VALUE的时候,直接不会走这个while,就会直接返回商为0

问题四

当被除数很大但除数很小时,减法操作执行的次数会很多。例如,求Integer.MAX_VALUE/1,减1的操作将执行2的31次方-1次,需要很长的时间。如果被除数是n,那么这种解法的时间复杂度为O(n)。所以我们可以将上述解法稍做调整。当被除数大于除数时,继续比较判断被除数是否大于除数的2倍,如果是,则继续判断被除数是否大于除数的4倍、8倍等。如果被除数最多大于除数的2k倍,那么将被除数减去除数的2k倍,然后将剩余的被除数重复前面的步骤。由于每次将除数翻倍,因此优化后的时间复杂度是O(logn)。

问题五

将负数转换成正数存在一个小问题。对于32位的整数而言,最小的整数是-2的31次方,最大的整数是2的31次方-1。因此,如果将-2的31次方转换为正数则会导致溢出。由于将任意正数转换为负数都不会溢出,因此可以先将正数都转换成负数,用前面优化之后的减法计算两个负数的除法,然后根据需要调整商的正负号。

正确的方法

    public static int DivisionMethodWithOutBug(int dividend, int divisor) {
        if (dividend == 0x80000000 && divisor == -1) {
            return Integer.MAX_VALUE;
        }
        int negative = 2;
        if (dividend > 0) {
            negative--;
            dividend = -dividend;
        }
        if (divisor > 0) {
            negative--;
            divisor = -divisor;
        }
        int result = divideCode(dividend, divisor);
        return negative == 1 ? -result : result;
    }


    private static int divideCode(int dividend, int divisor) {
        int result = 0;
        while (dividend <= divisor) {
            int value = divisor;
            int quotient = 1;
            while (value >= 0xc0000000 && dividend <= value + value) {
                quotient += quotient;
                value += value;
            }
            result += quotient;
            dividend -= value;
        }
        return result;
    }