题目:
给定两个整数,两数相除但不能用乘法、除法和mod运算符。
且:
- 被除数和除数均为 32 位有符号整数。
- 除数不为 0。
- 假设我们的环境只能存储 32 位有符号整数,其数值范围是 [ − 2 31 -2^{31} −231, 2 31 2^{31} 231 − 1]。本题中,如果除法结果溢出,则返回 2 31 2^{31} 231 − 1。
思路
简单来说,这题就是要求被除数中能分出多少个除数出来。
那么这道题难在哪里?
- 存储变量只能是int类型,不能是long等类型;
- 被除数和除数均有可能出现负数;
- 要考虑溢出的情况;
- 当被除数很大、除数很小时,要怎么算才能更快一些?
让我们逐一击破。
针对第一点,因为只能存储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=...+0∗divisor∗22+1∗divisor∗21+1∗divisor∗20
a
n
s
=
.
.
.
+
0
∗
2
2
+
1
∗
2
1
+
1
∗
2
0
ans = ... + 0*2^2 + 1*2^1 + 1*2^0
ans=...+0∗22+1∗21+1∗20
编程
一、首先要考虑一些特殊的情况:
- 被除数等于除数时,返回1;
- 除数为1时,返回被除数;
- 被除数等于最小的负数INI_MIN,除数等于-1时,会溢出,返回INT_MAX;
- 除数为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