29. Divide Two Integers(两数相除)两种解法(C++ & 注释)

本文深入探讨LeetCode第29题“两数相除”的解题策略,介绍不使用乘除法和mod运算符进行整数除法的两种方法:减法和位操作。通过实例代码展示减法方法的超时问题,并详细解析位操作如何优化效率。

1. 题目描述

给定两个整数,被除数 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− 1]。本题中,如果除法结果溢出,则返回 2^31 − 1。

题目链接:中文题目英文题目

2. 减法(Subtraction, Time Limit Exceeded)

2.1 解题思路

最简单的思路就是:被除数每减去一次除数,计数加一,当被除数小于0的时候,根据被除数和除数的符号返回总计数即可。只是需要考虑一个很极端的情况:

被除数:INT_MIN,除数:-1

这种情况的结果是INT_MAX + 1,明显超过了整型能表示的最大值,所以这里需要额外判断一下。但是这个方法当被除数很大,除数很小的时候,比如刚才的例子,非常的耗时,所以超时了。

2.2 实例代码

class Solution {
public:
    int divide(int dividend, int divisor) {
        bool minusOne = dividend >= 0 ? false : true, minusTwo = divisor >= 0 ? false : true;
        long long dividendTemp = dividend, divisorTemp = divisor;
        if (dividendTemp < 0) dividendTemp = -dividendTemp;
        if (divisorTemp < 0) divisorTemp = -divisorTemp;

        long long count = 0;
        while (dividendTemp >= 0) { count++; dividendTemp -= divisorTemp; }

        count--;
        if (minusOne && minusTwo || !minusOne && !minusTwo) {
            if (count >= INT_MAX) return INT_MAX;
            else return count;
        }
        return -count;
    }
};

3. 位操作(Bit Manipulation)

3.1 解题思路

上面的思路中是一个个地减去除数,如果想要节约时间,能不能一次性减去多个除数呢?也就是减去除数的倍数,但是题目要求不能使用乘法、除法和模运算,所以我们可以考虑使用位运算(左位移和右位移):

  1. 整型左位移1位,相当于原数 /= 2;并且移动的时候,最低位舍弃,负数最高位补1,正数最高位补0;
  2. 整型右位移1位,相等于原数 *= 2;并且最低位补0,最高位舍弃;

比如1二进制表示为0001(省略前面的0),左位移一位变成0000 = 0,右位移一位变成0010 = 2。比如-1二进制表示为1111(省略前面的1),左位移一位不变还是1111 = -1,右位移一位变成1110 = -2;

所以我们可以利用左位移的特性,让被除数减去偶数倍的除数,操作步骤如下:

  1. 初始化一个新的被除数(dividendTemp),一个新的除数(divisorTemp ),一个答案(ans),一个倍数(times)。
  2. 并且dividendTemp和divisorTemp 需要绝对值一下,保证计算时都是正数;
  3. 然后判断一下结果是复数还是正数;
  4. 如果除数是1,根据符号直接返回dividendTemp即可;
  5. 接下来,判断一下dividendTemp是不是大于两倍的divisorTemp (divisorTemp << 1),如果是divisorTemp扩大两倍,times也扩大两倍;一直重复这一步直到dividendTemp不在大于两倍的divisorTemp,跳出循环的时候ans += times;
  6. 接下来,判断一下dividendTemp是不是大于等于divisorTemp。如果是只需要ans += 1即可,因为这时dividendTemp最多只比divisorTemp大一倍
  7. 当dividendTemp < divisorTemp,已经做完除法,根据符号返回ans即可;

举个例子:dividendTemp = 14,divisorTemp = 3。14大于两倍的3(= 6),也大于两倍的6(= 12),所以14 - 12 = 2 < 3,循环结束,我们返回4即可。

其实这个思路可以理解成用除数的偶数倍来做除法,最后返回的答案就是除数是被除数的多少倍。当我们保证被除数不大于两倍的除数,这时被除数要么小于除数,直接舍去结果即可(因为整型计算,没有小数);被除数要么等于大于一倍的除数,小于两倍的除数,这时结果再加一即为最终结果。

3.2 实例代码

3.2.1 迭代实现

class Solution {
public:
    int divide(int dividend, int divisor) {
        if (dividend == INT_MIN && divisor == -1) return INT_MAX;
        long dividendTemp = labs(dividend), divisorTemp = labs(divisor), ans = 0;
        bool ifPositive = (dividend < 0) ^ (divisor < 0) ? false : true; // 结果是正数还是负数,异或运算:相同为0,不同为1
        if (divisorTemp == 1) return ifPositive ? dividendTemp : -dividendTemp;
        while (dividendTemp >= divisorTemp) {
            long divisorMultiply = divisorTemp, times = 1;
            while (dividendTemp >= divisorMultiply << 1) {
                divisorMultiply <<= 1;
                times <<= 1;
            }

            ans += times;
            dividendTemp -= divisorMultiply;
        }

        return ifPositive ? ans : -ans;
    }
};

3.2.2 递归实现

class Solution {
public:
    int divide(int dividend, int divisor) {
        long dividendTemp = labs(dividend), divisorTemp = labs(divisor);
        if (dividendTemp < divisorTemp) return 0;
        long divisorMulply = divisorTemp, times = 1, ans = 0;
        while (dividendTemp > (divisorMulply << 1)) { divisorMulply <<= 1; times <<= 1; }
        ans += times + divide(dividendTemp - divisorMulply, divisorTemp);
        if ((dividend < 0) ^ (divisor < 0)) ans = -ans;
        return ans >= INT_MAX ? INT_MAX : ans;
    }
};

4. 参考资料

  1. [LeetCode] 29. Divide Two Integers 两数相除
  2. C 库函数 - labs()
  3. abs
  4. labs
  5. 负数的位运算问题
  6. 位运算(&、|、^、~、>>、<<)

在这里插入图片描述

在 OpenCvSharp 3.x 中,两个矩阵相除可以通过 `Cv2.Divide` 方法实现。该方法支持两个输入矩阵之间的逐元素除法运算,并将结果存储在输出矩阵中。这种操作与 OpenCV 的除法规则一致,即饱和运算:当结果小于 0 时取 0,对于除法结果小于 1 的情况,若值大于 0.5 则取 1,否则取 0[^1]。 ### 示例代码 以下是一个完整的 C# 示例,展示如何在 OpenCvSharp 3.x 中进行两个矩阵的相除操作: ```csharp using OpenCvSharp; class Program { static void Main() { // 创建两个 CV_8UC1 类型的矩阵 Mat mat1 = new Mat(2, 2, MatType.CV_8UC1); mat1.SetTo(new Scalar(0)); mat1.Set&lt;byte&gt;(0, 0, 50); mat1.Set&lt;byte&gt;(0, 1, 100); mat1.Set&lt;byte&gt;(1, 0, 150); mat1.Set&lt;byte&gt;(1, 1, 200); Mat mat2 = new Mat(2, 2, MatType.CV_8UC1); mat2.SetTo(new Scalar(0)); mat2.Set&lt;byte&gt;(0, 0, 100); mat2.Set&lt;byte&gt;(0, 1, 50); mat2.Set&lt;byte&gt;(1, 0, 3); mat2.Set&lt;byte&gt;(1, 1, 2); // 创建输出矩阵 Mat result = new Mat(); // 执行矩阵相除 Cv2.Divide(mat1, mat2, result); // 输出结果 Console.WriteLine(&quot;mat1:&quot;); for (int i = 0; i &lt; mat1.Rows; i++) { for (int j = 0; j &lt; mat1.Cols; j++) { Console.Write(mat1.Get&lt;byte&gt;(i, j) + &quot; &quot;); } Console.WriteLine(); } Console.WriteLine(&quot;mat2:&quot;); for (int i = 0; i &lt; mat2.Rows; i++) { for (int j = 0; j &lt; mat2.Cols; j++) { Console.Write(mat2.Get&lt;byte&gt;(i, j) + &quot; &quot;); } Console.WriteLine(); } Console.WriteLine(&quot;result (mat1 / mat2):&quot;); for (int i = 0; i &lt; result.Rows; i++) { for (int j = 0; j &lt; result.Cols; j++) { Console.Write(result.Get&lt;float&gt;(i, j) + &quot; &quot;); } Console.WriteLine(); } } } ``` ### 运算规则说明 - OpenCV 的 `Divide` 方法遵循饱和运算规则,即在计算结果小于 0 时取最小值 0,而在结果小于 1 时,若大于 0.5 则取 1,否则取 0[^1]。 - 输出矩阵的数据类型通常为 `CV_32F`,即 32 位浮点数类型,以适应除法结果的小数值[^2]。 - 如果需要避免饱和运算并获得精确的浮点结果,可以先将输入矩阵转换为浮点类型(如 `CV_32F`),再执行除法操作。 ### 注意事项 - 确保输入矩阵的大小和类型一致,否则会抛出异常。 - 如果输入矩阵中存在除数为 0 的情况,OpenCV 会将其处理为 0 或根据上下文决定,避免程序崩溃。 - 在图像处理中,矩阵相除常用于图像归一化、背景抑制、特征增强等操作。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值