29. 两数相除

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 2311]。本题中,如果除法结果溢出,则返回 2 31 − 1 2^{31} − 1 2311


题解:

不让用 乘法、除法、取模 运算,那就只能用 减法 喽。

考虑最基础的做法:用 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=kdivisor+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=c020+c121+...+cm2m

题目回到 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 21divisor,22divisor,...,2mdivisor ,并且最后一项保证 2 m ∗ d i v i s o r ≤ d i v i d e n d 2^m*divisor \le dividend 2mdivisordividend

预处理完成后,我们就可以从高位到低位做减法:如果 d i v i d e n e d > = 2 d ∗ d i v i s o r dividened >= 2^d*divisor dividened>=2ddivisor ,那么 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%
*/

贴一个进阶版本:

不用算术运算实现整数的加减乘除运算

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值