leetcode 29.两数相除
题目描述
给定两个整数,被除数 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。
解题思路
两数两除,不能使用乘法、除法和mod运算符,那么一个很简单的思路就出来了,就是不断的用被除数不断的减去除数,直到被除数小于除数的时候,结束迭代,但是如果这样做的话,势必会超时,举个例子 dividend=INT_MIN
, divisor=1
,如果这样迭代的话,就要花费很长的时间。
在计算机里,位运算的执行效率是非常高的,一个数字左移一位相当于除以2,右移一位相当于乘以2,那么就可以采用这样的方法来减少迭代次数。意思是,每一次相减都让除数的值翻倍,当然累加次数也要翻倍,直到dividend小于divisor的时候停止。举几个简单的例子
例子一: dividend = 10, divisor = 1
-
采用保留求解,就是10不停的减去1,那么需要循环十次。
-
采用位移的方法 ,是怎么操作的呢,看下面的表格,
- 首先建立一个临时的变量temp=divisor,让dividend去减temp,count记一,这时候发现dividend大于等于temp,那么dividor翻倍,count也翻倍,然后发现dividend仍然大于等于temp,temp继续翻倍,那么count也应该在原来的基础上翻倍(记录被减了多少次);
- 再继续判断temp继续翻倍的话,dividend就小于temp了,这时候dividend=2,让此时的dividend去和divisor比较,发现dividend大于等于divisor,那么就继续把temp=divisor,循环上面的操作
上面写的步骤只是为了好理解,其实就是 dividend = 8 + 2 + 余数 = 23*1 + 21*1 + 余数
dividend temp count 10 >= 2 1 1 10 >= 4 2 2 10 >= 8 4 4 10 < 16,此时10减去8,并与divisor比较 8 8 此时res+8 2 >= 2 1 1 2 < 4 此时 2减去2,并与divisor比较 2 2 此时res+2 合计 res 10
例子二:dividend = 123, divisor = 3;
123 = 25*3 + 23*3 + 3
dividend | temp | count |
---|---|---|
123 >= 6 | 3 | 1 |
123 >= 12 | 6 | 2 |
123 >= 24 | 12 | 4 |
123 >= 48 | 24 | 8 |
123 >= 96 | 48 | 16 |
123 < 192 此时 123减去96,并与divisor比较 | 96 | 32 此时res+32 |
27 >= 6 | 3 | 1 |
27 >= 12 | 6 | 2 |
27 >= 24 | 12 | 4 |
27 <= 48 此时27减去24,并与divisor比较 | 24 | 8 此时res+8 |
3 < 6 此时 3减去3,并与divisor比较 | 3 | 1 此时res+1 |
合计 res | 41 |
通过上面的例子就可以知道,通过位移的方法可以大大加上迭代的次数,提高运算效率,具体代码如下
class Solution {
public:
int divide(int dividend, int divisor) {
if(divisor == 0 || dividend == 0){ // 被除数为零或者除数为零直接返回零
return 0;
}
if(divisor == 1){ // 如果除数是1,就不需要额外的操作,直接返回被除数本身
return dividend;
}
// 当被除数为INT_MIN, 除数是-1时需要特别注意,会出现溢出的情况,因为int范围是-2147483648~2147483647,相除的结果就是2147483648,当然是溢出;所以这里做一个特殊处理
if(divisor == -1 && dividend == INT_MIN){
return INT_MAX;
}
bool sign = (dividend > 0)^(divisor > 0); // 通过异或, 判断是不是异号
long a = dividend, b = divisor; // 这里把两个数转成long类型,并取绝对值,取绝对值是为了好运算,如果是不采用绝对值,就要分四种情况(--,++,+-,-+)来讨论反而更复杂
a = abs(a);
b = abs(b);
int res = 0;
while(a >= b){ // 如果a<b,那么循环终止
int count = 1;
long temp = b;
while(a >= (temp<<1)){ // 不断的采用位移操作,尽可能减去最大的除数的倍数
temp = temp << 1;
count = count << 1;
}
a -= temp;
res += count;
}
// 通过sign判断是否异号,并在结果res上添加对应符号
return sign ? -res : res;
}
};
欢迎大家关注我的个人公众号,同样的也是和该博客账号一样,专注分享技术问题,我们一起学习进步