题目描述
给定两个整数,被除数 dividend 和除数 divisor。将两数相除,要求不使用乘法、除法和 mod 运算符。
返回被除数 dividend 除以除数 divisor 得到的商。
示例 1:
输入: dividend = 10, divisor = 3
输出: 3
示例 2:
输入: dividend = 7, divisor = -3
输出: -2
说明:
被除数和除数均为 32 位有符号整数。
除数不为 0。
假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−231, 231 − 1]。本题中,如果除法结果溢出,则返回 231 − 1。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/divide-two-integers
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路
- 本人傻逼思路,没看到不给用除法,不过线下线上结果不一致也很迷
class Solution {
public:
int divide(int dividend, int divisor) {
if(dividend == -2147483648&& divisor==-1){
return 2147483647;
}
if(dividend==0){
return 0;
}
if(abs(dividend)<abs(divisor)){
return 0;
}
if(dividend==divisor){
return 1;
}
int ret = 1;
if(dividend>0&&divisor>0){
while(ret*divisor<=dividend){
ret++;
}
return ret-1;
}else if(dividend>0&&divisor<0){
divisor*=-1;
while(ret*divisor<=dividend){
ret++;
}
return -1*(ret-1);
}else if(dividend<0&&divisor>0){
dividend*=-1;
while(ret*divisor<=dividend){
ret++;
}
return -1*(ret-1);
}else if(dividend<0&&divisor<0){
dividend*=-1;
divisor*=-1;
while(ret*divisor<=dividend){
ret++;
}
return ret-1;
}
return 0;
}
};
二分
题目分类提示了二分查找,那么按二分查找的思路去想就行了,既然要求不能使用乘除模运算,那么就让除数不断自加倍增,与被除数对比,倍增过头了就初始化为原始的除数值再次循环倍增,循环过程中被除数减去除数不断减小,直到小于除数为止,其过程就是把二分查找中的 mid=(left+right)/2 替换成了divisor*2,把 left 和 right 替换成了 divisor_tmp 和 divideng_tmp,这样理解就比较直观了。
class Solution {
public int divide(int dividend, int divisor) {
/** 除数为零就返回-1 按照测试样例的要求写的*/
if (divisor==0)
return -1;
if (dividend==0)
return 0;
/** -2147483648, -1 这个测试样例的确没想到,结果翻车了*/
if (dividend==Integer.MIN_VALUE && divisor==-1)
return Integer.MAX_VALUE;
/** 符号位的处理参考了大佬的异或处理方法*/
boolean negetive= (dividend^ divisor)<0;
/** div_count 是当前divisor_tmp相对于divisor的倍数 */
int res=0, div_count=1;
/** 因为值溢出之后边界问题处理太繁琐了,直接将数值转为long省去麻烦 */
long dividend_tmp= Math.abs((long)dividend);
long divisor_tmp= Math.abs((long)divisor);
/** 按标准的二分查找代码模板写的 */
while (dividend_tmp>= divisor_tmp) {
dividend_tmp-= divisor_tmp;
res+= div_count;
if (dividend_tmp< Math.abs(divisor))
break;
/** divisor_tmp无法倍增时,就将其初始化为divisor绝对值,重新开始下一轮倍增*/
if (dividend_tmp- divisor_tmp< divisor_tmp) {
divisor_tmp= Math.abs(divisor);
div_count=1;
continue;
}
/** 不断倍增divisor_tmp直到和dividend_tmp一样大*/
divisor_tmp+= divisor_tmp;
div_count+= div_count;
}
return negetive? 0-res: res;
}
}
/*
作者:ao-ye-zhen-tou
链接:https://leetcode-cn.com/problems/divide-two-integers/solution/an-er-fen-cha-zhao-mo-ban-xie-de-by-ao-ye-zhen-tou/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*/
数学方法
显然,用对数和幂运算性质,有 y/x=e^(lny-lnx)。先把除数和被除数取绝对值,divide = exp(log(dividend) - log(divisor))。如果取正后,溢出,则先把原来的数右移/左移一位,计算后,把结果再左移/右移一位。注意精度问题。
倍增思想
不使用乘法、除法和 mod 运算符来完成除法,那么就只能使用减法了,一种方法是先将除数和被除数都转换为正数,然后不断地从被除数中减去除数,减一个除数商就加一。但是这种方法显得太慢了,因此考虑在迭代中倍增除数,这样商就可以每次 +1 +2 +4 …。在某一时刻,当被除数小于除数的时候,把除数还原为初始值,然后继续倍增。
class Solution {
public int divide(int dividend, int divisor) {
int sign = (dividend ^ divisor) >> 31;
long lDividend = Math.abs((long) dividend);
long lDivisor = Math.abs((long) divisor);
long res = 0;
while (lDividend >= lDivisor){
long tmp = lDivisor;
long i = 1;
while (lDividend >= tmp){
lDividend -= tmp;
res += i;
i <<= 1;
tmp <<= 1;
}
}
if (sign == -1) res *= -1;
if (res < Integer.MIN_VALUE) return Integer.MIN_VALUE;
else if (res > Integer.MAX_VALUE) return Integer.MAX_VALUE;
return (int)res;
}
}
倍增的进阶版,指数增
* 解题思路:这题是除法,所以先普及下除法术语
* 商,公式是:(被除数-余数)÷除数=商,记作:被除数÷除数=商...余数,是一种数学术语。
* 在一个除法算式里,被除数、余数、除数和商的关系为:(被除数-余数)÷除数=商,记作:被除数÷除数=商...余数,
* 进而推导得出:商×除数+余数=被除数。
*
* 要求商,我们首先想到的是减法,能被减多少次,那么商就为多少,但是明显减法的效率太低
*
* 那么我们可以用位移法,因为计算机在做位移时效率特别高,向左移1相当于乘以2,向右位移1相当于除以2
*
* 我们可以把一个dividend(被除数)先除以2^n,n最初为31,不断减小n去试探,当某个n满足dividend/2^n>=divisor时,
*
* 表示我们找到了一个足够大的数,这个数*divisor是不大于dividend的,所以我们就可以减去2^n个divisor,以此类推
*
* 我们可以以100/3为例
*
* 2^n是1,2,4,8...2^31这种数,当n为31时,这个数特别大,100/2^n是一个很小的数,肯定是小于3的,所以循环下来,
*
* 当n=5时,100/32=3, 刚好是大于等于3的,这时我们将100-32*3=4,也就是减去了32个3,接下来我们再处理4,同样手法可以再减去一个3
*
* 所以一共是减去了33个3,所以商就是33
*
* 这其中得处理一些特殊的数,比如divisor是不能为0的,Integer.MIN_VALUE和Integer.MAX_VALUE
*
优美的代码
public int divide(int dividend, int divisor) {
if (dividend == 0) {
return 0;
}
if (dividend == Integer.MIN_VALUE && divisor == -1) {
return Integer.MAX_VALUE;
}
boolean negative;
negative = (dividend ^ divisor) <0;//用异或来计算是否符号相异
long t = Math.abs((long) dividend);
long d= Math.abs((long) divisor);
int result = 0;
for (int i=31; i>=0;i--) {
if ((t>>i)>=d) {//找出足够大的数2^n*divisor
result+=1<<i;//将结果加上2^n
t-=d<<i;//将被除数减去2^n*divisor
}
}
return negative ? -result : result;//符号相异取反
}
C++版本
这里限制了只能用int,因此没有使用long,增加了许多强转
#define LIMIT 0x80000000
static int divide(int dividend, int divisor) {
if (dividend == 0) {
return 0;
}
if (dividend == INT_MIN && divisor == -1) {
return INT_MAX;
}
bool negative;
negative = (dividend ^ divisor) < 0;//用异或来计算是否符号相异
unsigned int t = dividend == INT_MIN ? LIMIT : abs(dividend);
unsigned int d = divisor == INT_MIN ? LIMIT : abs(divisor);
unsigned int result = 0;
for (int i = 31; i >= 0; i--) {
if ((t >> i) >= d) {//找出足够大的数2^n*divisor
result += ((unsigned int)1) << i;//将结果加上2^n
t -= d << i;//将被除数减去2^n*divisor
}
}
if (result == LIMIT) {//特殊数不能将unsigned int转为int
return INT_MIN;
}
else {
return negative ? -(int)result : (int)result;//符号相异取反
}
}