【Leetcode】29. 两数相除

Given two integers dividend and divisor, divide two integers without using multiplication, division, and mod operator.

The integer division should truncate toward zero, which means losing its fractional part. For example, 8.345 would be truncated to 8, and -2.7335 would be truncated to -2.

Return the quotient after dividing dividend by divisor.

Note:
Assume we are dealing with an environment that could only store integers within the 32-bit signed integer range: [ − 2 31 , 2 31 − 1 ] [−2^{31}, 2^{31} − 1] [231,2311]. For this problem, if the quotient is strictly greater than 2 31 − 1 2^{31} - 1 2311, then return 2 31 − 1 2^{31} - 1 2311, and if the quotient is strictly less than − 2 31 -2^{31} 231, then return − 2 31 -2^{31} 231.

Example 1:

Input: dividend = 10, divisor = 3
Output: 3
Explanation: 10/3 = 3.33333.. which is truncated to 3.

Example 2:

Input: dividend = 7, divisor = -3
Output: -2
Explanation: 7/-3 = -2.33333.. which is truncated to -2.

Constraints:

-231 <= dividend, divisor <= 231 - 1
divisor != 0

AC:

/*
 * @lc app=leetcode.cn id=29 lang=cpp
 *
 * [29] 两数相除
 */

// @lc code=start
class Solution {
public:
    int divide(int dividend, int divisor) {
        // 特殊情况处理
        if(dividend == INT_MIN) {
            if(divisor == 1) {
                return INT_MIN;
            }
            if(divisor == -1) {
                return INT_MAX;
            }
        }

        if(divisor == INT_MIN) {
            return dividend == INT_MIN ? 1 : 0;
        }

        if(dividend == 0) {
            return 0;
        }

        bool rev = false;
        // 将被除数和除数转换为负数,方便处理
        if(dividend > 0) {
            dividend = -dividend;
            rev = !rev;
        }
        if(divisor > 0) {
            divisor = -divisor;
            rev = !rev;
        }

        // 用于计算两个负数相乘的结果是否超出了 int 类型的范围。
        auto quickAdd = [](int y, int z, int x) {
            int result = 0, add = y;
            while(z) {
                //判断 z 是否为奇数
                if(z & 1) {
                    if(result < x - add)
                    {
                        return false;
                    }
                    result += add;
                }
                //用于计算两个负数相乘的结果是否超出了 int 类型的范围。
                //如果超出了范围,就返回 false,否则将结果加到 result 中。
                if(z != 1) {
                    if(add < x - add)
                    {
                        return false;
                    }
                    add += add;
                }
                z >>= 1;
            }
            return true;
        };

        int left = 1, right = INT_MAX, ans = 0;
        // 二分查找
        while(left <= right) {
            int mid = left +((right - left) >> 1);
            bool check = quickAdd(divisor, mid, dividend);
            if(check) {
                ans = mid;
                if(mid == INT_MAX) {
                    break;
                }
                left = mid + 1;
            }
            else {
                right = mid - 1;
            }
        }
        return rev ? -ans : ans;
    }
};
// @lc code=end

【Leetcode】29. 两数相除_第1张图片

Thought:
(code不出,参考了官方题解)

  1. 首先处理特殊情况,如果被除数为 INT_MIN,那么当除数为 1 时,结果为 INT_MIN,当除数为 -1 时,结果为 INT_MAX。如果除数为 INT_MIN,那么当被除数为 INT_MIN 时,结果为 1,否则结果为 0。如果被除数为 0,那么结果为 0

  2. 将被除数和除数转换为负数,方便处理。同时记录一个变量 rev,表示最终结果是否为负数。

  3. 定义一个函数 quickAdd,用于计算两个负数相乘的结果是否超出了 int 类型的范围。如果超出了范围,就返回 false,否则将结果加到 result 中。

  4. 二分查找,查找范围为 [1, INT_MAX],每次取中间值 mid,然后调用 quickAdd 函数判断 divisor * mid 是否小于等于 dividend。如果小于等于,说明 mid 可以作为结果,更新 ans 的值,并将查找范围缩小到 [mid + 1, right],否则将查找范围缩小到 [left, mid - 1]

  5. 最后根据 rev 的值返回结果,如果为 true,说明结果为负数,需要将 ans 取反。


  • 第二种做法也不错,个人感觉比第一种做法难想,毕竟第一种是快速乘法。
    【Leetcode】29. 两数相除_第2张图片

Supplements
快速乘法快速幂是两个不同的算法。

快速乘法用于高精度乘法,可以在 O ( n log ⁡ n ) O(n \log n) O(nlogn) 的时间复杂度内完成两个 n n n 位数的乘法。其基本思想是将整数按位拆分成较小的部分,然后在乘法运算中通过组合这些部分来得到结果。例如,对于两个 n n n 位数 a a a b b b,可以将它们分别拆分成 n / 2 n/2 n/2 位的高半部分 a 1 a_1 a1 b 1 b_1 b1,以及 n / 2 n/2 n/2 位的低半部分 a 0 a_0 a0 b 0 b_0 b0,然后使用如下公式计算乘积:

a b = a 1 b 1 ⋅ 1 0 2 ( n / 2 ) + ( a 1 b 0 + a 0 b 1 ) ⋅ 1 0 n / 2 + a 0 b 0 ab = a_1b_1 \cdot 10^{2(n/2)} + (a_1b_0 + a_0b_1) \cdot 10^{n/2} + a_0b_0 ab=a1b1102(n/2)+(a1b0+a0b1)10n/2+a0b0

这样,原来的乘法问题就被转化成了四个规模为 n / 2 n/2 n/2 的乘法问题和一些小规模的加法和移位问题。

相比传统的暴力乘法算法,快速乘法可以极大地加速乘法运算,尤其是在需要处理大数乘法时,效果更为明显。

快速幂用于求幂运算,即计算一个数的 n n n 次幂。它可以在 O ( log ⁡ n ) O(\log n) O(logn) 的时间复杂度内完成这个计算。其基本思想是将幂运算拆分成多个指数为 2 2 2 的幂次方相乘的形式,例如:

a 5 = a ( 2 2 + 2 0 ) = a 2 2 ⋅ a 2 0 = a 4 ⋅ a a^5 = a^{(2^2 + 2^0)} = a^{2^2} \cdot a^{2^0} = a^4 \cdot a a5=a(22+20)=a22a20=a4a

这样,原问题就被转化成了两个规模为 n / 2 n/2 n/2 的子问题,每个子问题都只需要计算一次。通过递归调用,可以在 O ( log ⁡ n ) O(\log n) O(logn) 的时间复杂度内完成整个运算过程。

相比传统的暴力幂算法,快速幂可以大幅度降低计算次数,从而提高幂运算的效率。快速乘法和快速幂是两个不同的算法。

快速乘法用于高精度乘法,可以在 O ( n log ⁡ n ) O(n \log n) O(nlogn) 的时间复杂度内完成两个 n n n 位数的乘法。其基本思想是将整数按位拆分成较小的部分,然后在乘法运算中通过组合这些部分来得到结果。例如,对于两个 n n n 位数 a a a b b b,可以将它们分别拆分成 n / 2 n/2 n/2 位的高半部分 a 1 a_1 a1 b 1 b_1 b1,以及 n / 2 n/2 n/2 位的低半部分 a 0 a_0 a0 b 0 b_0 b0,然后使用如下公式计算乘积:

a b = a 1 b 1 ⋅ 1 0 2 ( n / 2 ) + ( a 1 b 0 + a 0 b 1 ) ⋅ 1 0 n / 2 + a 0 b 0 ab = a_1b_1 \cdot 10^{2(n/2)} + (a_1b_0 + a_0b_1) \cdot 10^{n/2} + a_0b_0 ab=a1b1102(n/2)+(a1b0+a0b1)10n/2+a0b0

这样,原来的乘法问题就被转化成了四个规模为 n / 2 n/2 n/2 的乘法问题和一些小规模的加法和移位问题。

相比传统的暴力乘法算法,快速乘法可以极大地加速乘法运算,尤其是在需要处理大数乘法时,效果更为明显。

快速幂用于求幂运算,即计算一个数的 n n n 次幂。它可以在 O ( log ⁡ n ) O(\log n) O(logn) 的时间复杂度内完成这个计算。其基本思想是将幂运算拆分成多个指数为 2 2 2 的幂次方相乘的形式,例如:

a 5 = a ( 2 2 + 2 0 ) = a 2 2 ⋅ a 2 0 = a 4 ⋅ a a^5 = a^{(2^2 + 2^0)} = a^{2^2} \cdot a^{2^0} = a^4 \cdot a a5=a(22+20)=a22a20=a4a

这样,原问题就被转化成了两个规模为 n / 2 n/2 n/2 的子问题,每个子问题都只需要计算一次。通过递归调用,可以在 O ( log ⁡ n ) O(\log n) O(logn) 的时间复杂度内完成整个运算过程。

相比传统的暴力幂算法,快速幂可以大幅度降低计算次数,从而提高幂运算的效率。


添加一个递归实现的做法:

/*
 * @lc app=leetcode.cn id=29 lang=cpp
 *
 * [29] 两数相除
 */

// @lc code=start
class Solution {
public:
    int divide(int dividend, int divisor) {
        if(dividend == INT_MIN && divisor == -1)
            return INT_MAX;
        if(dividend == INT_MIN && divisor == 1)
            return INT_MIN;
        if(dividend > 0)
            return -divide(-dividend, divisor); // 如果被除数为正数,则将被除数和除数都取相反数,这样就可以转化为被除数为负数的情况
        if(divisor > 0)
            return -divide(dividend, -divisor); // 如果除数为正数,则将被除数和除数都取相反数,这样就可以转化为除数为负数的情况
        if(dividend > divisor)
            return 0; // 如果被除数小于除数,则返回0
        int res = 1, tmp = divisor;
        while((dividend - divisor) <= divisor)
        {
            res += res;
            divisor += divisor;
        }
        return res + divide(dividend - divisor, tmp); // 递归计算被除数减去除数的结果和除数的商
    }
};
// @lc code=end

你可能感兴趣的:(Leetcode,leetcode,算法,职场和发展)