题目

Divide two integers without using multiplication, division and mod operator.

If it is overflow, return MAX_INT.


考察点

算法

投机算法:将除法化成对数函数的减法问题

暴力算法:一直累减

改进算法:利用二进制。为什么是二进制?因为方便用位运算。
暴力算法减的是一个常数,改进算法减的是一个动态最大的数。
有点像学习算法时候引入的最大公因数的算法。

溢出问题

投机的做法,强制转成long型

  1. Java的int型是32位的,范围在-2^31 和 2^31-1 之间, 即-2147483648 和2147483647之间。

  2. Math.abs(int)返回的是int型。必须用Math.abs(long)要想返回long。

  3. <<左移 >>右移位运算符返回的是int型

关于边界数举例
-         1: 11111111111111111111111111111111
-2147483647: 10000000000000000000000000000001
(负数到正数,先-1再取反)
+2147483647: 01111111111111111111111111111111
(正数到负数,先取反再+1)
+2147483648: 10000000000000000000000000000000
-2147483648: 10000000000000000000000000000000
左移一位(符号不保留)
-0000000000: 00000000000000000000000000000000
(2147483648超出32位边界,实际无法赋值,只能通过214748367+1得到)
由上发现,±2147483648在int型中编码是一致的。原因是按补码规则进位到最高位的信息被截去了,所以出现了两个数用同一种编码。


代码

二进制算法

主要思路
位移 等价于 乘除法

一、 非递归

eg 17/3=5...2

17=3x5+2
17=3x( 4 + 0 + 1 )+2
17=3x(1x2^2+0x2^1+1x2^0)+2

主要思路就是先找到最大n 满足17≥3x2^n。

然后从高位到低位把每个二进制位到底是0还是1判别一下。
n=2时, 17 > 3x1x2^2 该二进制位取1
n=1时, 17-12=5 < 3x1x2^1 该二进制位取0
n=0时, 5 > 3x1x2^0 该二进制位取1

最后根据二进制位权重加和就是所要求的商。
因为不能用乘除法,所以选择二进制进行位移是一个比较好的选择。因为将3左移1位即等同于3乘以2。

另外一种思路是一样的,只不过把左移除数变成了右移被除数
n=2时, 17/4 > 3 取1
n=1时, 5/2 < 3 取0
n=0时, 5/1 > 3 取1

    public int divide1(int b, int d) {
        if (d == 0 || b == Integer.MIN_VALUE && d==-1) return Integer.MAX_VALUE;
        else if(b == Integer.MIN_VALUE && d==1) return Integer.MIN_VALUE;
        long r = 0;
        int sign = (b > 0 && d > 0) || (b < 0 && d < 0) ? 1 : -1;
        //int  flag = (b >> 31) ^ (d >> 31); //有趣的符号判断方法 相同flag为0,相异flag为1
        long lb = Math.abs((long)b);//!!!!!如果不加long, abs()返回的仍是-2147483648
        long ld = Math.abs((long)d);

        long digit = 0;
        while (lb >= ld) {
            ld <<= 1;
            digit++;
        }

        ld>>= digit; //1. 除数ld的左移和digit同时多运算了一次,因此直接使用digid即可。
                     //2. 要用除数ld先扩大后减小。 如果用被除数先减小为0,就左移扩大不回去了。
        digit--; //加1之后 判断不满足,因此要减回去
        for (long i = digit; i >= 0; i--) {
            if (lb >> i >= ld) {
                //long test=1<<i; //!!!!!这里返回的居然是int。。。如果是lb=-2147483648 就跪了,因此只能在开头加限制条件。
                r += 1 << i;
                lb -= ld << i;
            }
        }
        return (int) (sign > 0 ? r : -r);
    }    

二、 递归

忽略边界条件

以17/3为例子
(17,3,0) (17,6,1) (17,12,2)
寻找小于17的最大除数12,(这个最大除数是3的2^n倍) 17>12 且 17<24 。(此时只能扩大除数。因为减小被除数会导致信息丧失)
找到后被除数=17-12;除数=12/2;二进制第n位-1
(5,6,1) (5,3,0)
判断什么时候终止递归。即二进制第n位==0的时候。如果被除数<除数,返回0;反之,返回1。

    private long divide_help(long b, long d, int digit) {
        
        if (b < d) {
            if (digit == 0) return 0;
            else return divide_help(b, d >> 1, digit - 1);
        } else if (b >= d << 1) //
            return divide_help(b, d << 1, digit + 1);
        else if (digit == 0) 
            return 1;
        else 
            return (1 << digit) + divide_help(b - d, d >> 1, digit - 1);
    }

投机算法:对数减法

忽略边界条件

    //b被除数,d除数,r商
    public int divide3(int b, int d) {
        int  flag = (b >> 31) ^ (d >> 31)
        long lb = Math.abs((long) b);
        long ld = Math.abs((long) d);
        
        //主方法其实就是下面的一句话
        double r = Math.exp(Math.log(lb)-Math.log(ld));

        return (int) (flag== 0 ? r : -r);
    }    

溢出问题探究

为什么会发生溢出问题?主要发生在-2147483648身上,因为它没有办法变成+2147483648。
而我们的计算都是按照正数来做的。
最后采取了所有计算按照负数来做。

非递归

    public int divide1_2(int b, int d) {
        if (d == 0 || b == Integer.MIN_VALUE && d == -1) return Integer.MAX_VALUE; //超出上限,一定要写
        //else if (d == 1) return b; 可写可不写

        int r = 0;
        int sign = (b > 0 && d > 0) || (b < 0 && d < 0) ? 1 : -1;
        if (b > 0) b = ~b + 1; //取负数
        if (d > 0) d = ~d + 1;

        int digit = 0;
        while (b - d <= d) { //原本写的是 while (b >= d<<2 ) 一是会发生溢出。二是负数,故改变了不等号的方向。
            d <<= 1;
            digit++;
        }
        d >>= digit;

        for (int i = digit; i >= 0; i--) {
            if (b <= d << i) {
                r += 1 << i;
                b -= d << i;
            }
        }
        return sign > 0 ? r : -r;
    }

递归

    public int divide2_2(int b, int d) {
        if (d == 0 || b == Integer.MIN_VALUE && d == -1) return Integer.MAX_VALUE;
        // else if (d == 1) return b;

        int flag = (b >> 31) ^ (d >> 31);
        if (b > 0) b = ~b + 1;
        if (d > 0) d = ~d + 1;
        int r = divide_help_negative(b, d, 0);
        return flag == 0 ? r : -r;
    }

    //改变不等号方向即可
    private int divide_help_negative(int b, int d, int digit) {
        if (b > d) {
            if (digit == 0) return 0;
            else return divide_help_negative(b, d >> 1, digit - 1);
        } else if (b - d <= d) return divide_help_negative(b, d << 1, digit + 1);
        else if (digit == 0) return 1;
        else return (1 << digit) + divide_help_negative(b - d, d >> 1, digit - 1);
    }

lindsay_bubble
26 声望11 粉丝

引用和评论

0 条评论