29. 两数相除
题目描述
给定两个整数,被除数 dividend
和除数 divisor
。将两数相除,要求不使用乘法、除法和 mod 运算符。
返回被除数 dividend
除以除数 divisor
得到的商。
整数除法的结果应当截去(truncate
)其小数部分,例如:truncate(8.345) = 8
以及 truncate(-2.7335) = -2
示例1:
输入: dividend = 10, divisor = 3
输出: 3
解释: 10/3 = truncate(3.33333..) = truncate(3) = 3
示例2:
输入: dividend = 7, divisor = -3
输出: -2
解释: 7/-3 = truncate(-2.33333..) = -2
提示:
被除数和除数均为 32 位有符号整数。
除数不为 0。
假设我们的环境只能存储 32 位有符号整数,其数值范围是 [
−
2
31
−2^{31}
−231,
2
31
−
1
2^{31} − 1
231−1]。本题中,如果除法结果溢出,则返回
2
31
−
1
2^{31} − 1
231−1。
题解:
不让用 乘法、除法、取模 运算,那就只能用 减法 喽。
考虑最基础的做法:用 dividend
一个个的去减 divisor
,似乎可行?考虑极限情况:
d
i
v
i
d
e
n
d
=
−
2
31
,
d
i
v
i
s
o
r
=
−
1
dividend=-2^{31}, divisor=-1
dividend=−231,divisor=−1,这种情况下,时间复杂度爆炸。
有这么一个直观想法:是不是每次减去的数越大,做减法的次数越少?
那么我们就可以这样考虑:
由于: d i v i d e n d d i v i s o r = k ∗ d i v i s o r + r \frac{dividend}{divisor} = k * divisor + r divisordividend=k∗divisor+r ,而 k = c 0 ∗ 2 0 + c 1 ∗ 2 1 + . . . + c m ∗ 2 m k = c_0 * 2^0 + c_1 * 2^1 + ... + c_m * 2^m k=c0∗20+c1∗21+...+cm∗2m。
题目回到 k
的二进制表示上来了,于是我们可以采用 倍增 的方式预处理 divisor
2 的整数次幂倍数:
2
1
∗
d
i
v
i
s
o
r
,
2
2
∗
d
i
v
i
s
o
r
,
.
.
.
,
2
m
∗
d
i
v
i
s
o
r
2^1*divisor, 2^2*divisor, ..., 2^m*divisor
21∗divisor,22∗divisor,...,2m∗divisor ,并且最后一项保证
2
m
∗
d
i
v
i
s
o
r
≤
d
i
v
i
d
e
n
d
2^m*divisor \le dividend
2m∗divisor≤dividend。
预处理完成后,我们就可以从高位到低位做减法:如果
d
i
v
i
d
e
n
e
d
>
=
2
d
∗
d
i
v
i
s
o
r
dividened >= 2^d*divisor
dividened>=2d∗divisor ,那么 k
的第 d+1
位肯定为 1 。
为了方便处理,我们使用 sign
记录两数相除后的符号,然后转换成正数来计算。
同时,因为
−
2
31
-2^{31}
−231 转为正数,若用 int
来保存,会溢出,所以考虑使用 long long
保存。
代码:
class Solution {
public:
using LL = long long;
int divide(int dividend, int divisor) {
bool sign = ((dividend >> 31) ^ (divisor >> 31));
if ( dividend == INT_MIN && divisor == -1 ) return INT_MAX;
if ( dividend == INT_MIN && divisor == 1 ) return INT_MIN;
LL a = abs( dividend ), b = abs( divisor );
vector<LL> exp;
for ( LL i = b; i <= a; i += i ) exp.push_back( i );
LL ret = 0;
for ( int i = exp.size() - 1; i >= 0; --i ) {
if ( a >= exp[i] ) {
a -= exp[i];
ret += 1 << i;
}
}
if ( ret > INT_MAX || ( sign && -ret < INT_MIN ) ) return INT_MAX;
return sign ? -ret : ret;
}
};
/*
时间:0ms,击败:100.00%
内存:6MB,击败:91.96%
*/
上面的代码使用 long long
的主要原因是因为
−
2
31
-2^{31}
−231 转为正数的时候会产生溢出,如果我们把数字都转为负数处理,可以不用 long long
。
具体见代码:
class Solution {
public:
int divide(int dividend, int divisor) {
const int up = (-1 << 31) >> 1;
if ( dividend == INT_MIN && divisor == -1 ) return INT_MAX;
if ( dividend == INT_MIN && divisor == 1 ) return INT_MIN;
// if ( dividend == INT_MAX && divisor == 1 ) return INT_MAX;
// if ( dividend == INT_MAX && divisor == -1 ) return -INT_MAX;
if ( !dividend ) return 0;
bool sign = ((dividend >> 31) ^ (divisor >> 31));
if ( dividend > 0 ) dividend = -dividend;
if ( divisor > 0 ) divisor = -divisor;
vector<int> exp;
for ( int i = divisor; i >= dividend; i += i ) {
exp.push_back( i );
if ( i < up ) break;
}
int ret = 0;
for ( int i = exp.size() - 1; i >= 0; --i ) {
if ( dividend <= exp[i] ) {
dividend -= exp[i];
ret += 1 << i;
}
}
return sign ? -ret : ret;
}
};
/*
时间:0ms,击败:100.00%
内存:6MB,击败:92.52%
*/
贴一个进阶版本: