题目
Divide two integers without using multiplication, division and mod operator.
If it is overflow, return MAX_INT.
考察点
算法
投机算法:将除法化成对数函数的减法问题
暴力算法:一直累减
改进算法:利用二进制。为什么是二进制?因为方便用位运算。
暴力算法减的是一个常数,改进算法减的是一个动态最大的数。
有点像学习算法时候引入的最大公因数的算法。
溢出问题
投机的做法,强制转成long型
Java的int型是32位的,范围在-2^31 和 2^31-1 之间, 即-2147483648 和2147483647之间。
Math.abs(int)返回的是int型。必须用Math.abs(long)要想返回long。
<<左移 >>右移位运算符返回的是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);
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。