问题描述:
- 给定两个整数
a
和b
,求它们的除法的商a/b
,要求不得使用乘号、除号以及求余符号。- 整数除法的结果应当截去其小数部分。【即向下取整】
- 假设环境只能存储 32 位有符号整数,其数值范围是 [ − 2 31 , 2 31 − 1 ] [−2^{31}, 2^{31}−1] [−231,231−1]。
- 如果除法结果溢出,则返回 2 31 − 1 2^{31}−1 231−1。
核心思路:
- 最朴素的方法是很直观的,首先假设
a
和b
均为正整数,一直使用除数a
减去被除数b
,直到a < b
,其中记录并更新进行减操作的次数,相当于以步进的方式更新商:int ans = 0; // 商 while(a >= b) { a -= b; ++ans; }
- 但步进的实现只能通过部分案例,因为假若分子分母相差较大,则很容易超时,此时需要考虑如何加快搜索,而利用快速乘的思想(类似于快速幂)可以对算法进行优化。
- 优化后不再采取步进的方式,而是通过倍增被除数
b
来找到商:
int ans = 0; // 最终的商 while(a >= b) { int tmp = b; // 被除数需要倍增,所以用一个新的变量来进行更新 int c = 1; // 当前的商,用来倍增作记录,后续会更新到ans中去 while(tmp+tmp <= a) { tmp += tmp; // tmp <<= 1 c += c; // c <<= 1 } a -= tmp; ans += c; }
- 优化后不再采取步进的方式,而是通过倍增被除数
- 目前的思路只考虑
a
和b
均为正整数的情况,且没有考虑很多的溢出情况,后续只需要对所有可能溢出的地方进行处理即可。【但通常处理溢出都很繁琐】 - 后续代码实现需要注意的地方有:
- 将
a
和b
均置为负数,是因为INT_MIN
的绝对值要比INT_MAX
更大,因而为了避免将INT_MIN
取反导致溢出,则将两个数置为负数,最后再将结果添上负号更方便。【取为负数后,前面的思路很多地方需要修改,如while(a >= b)
需要改为判断while(a <= b)
】 - 在 while 循环中需要判断
tmp <= INT_MIN >> 1
,避免tmp + tmp
溢出。 - 还要注意在代码中
ans
和c
均为unsigned
,这是因为 leetcode 中带的 C++ 编译器不允许 int 溢出,如果溢出会直接报错。【这里倍增c
时产生的溢出对于题目来说是允许的】
- 将
代码实现:
class Solution
{
private:
int INT_LIMIT = INT_MIN >> 1; // -1073741824
public:
int divide(int a, int b)
{
if(a == INT_MIN and b == -1) return INT_MAX;
bool neg = ((a >> 31) ^ (b >> 31)) != 0; // 位运算确定符号
if(a > 0) a = -a;
if(b > 0) b = -b;
unsigned ans = 0;
while(a <= b)
{
int tmp = b; // 当前除数
unsigned c = 1; // 当前商
while(tmp >= INT_LIMIT and a <= tmp + tmp) // 快速乘法
{
tmp += tmp;
c += c;
}
a -= tmp; // 求得a剩余部分
ans += c; // 累计商
}
return neg ? -ans : ans;
}
};