【问题标题】:What is the time complexity of this division function (no divide or multiply operators used)?这个除法函数的时间复杂度是多少(没有使用除法或乘法运算符)?
【发布时间】:2019-05-22 20:59:56
【问题描述】:

我解决了这个 leetcode 问题 https://leetcode.com/problems/divide-two-integers/ 。目标是在不使用乘法或除法运算符的情况下获得dividend 除以divisor 的商。这是我的解决方案:

    def divide(dividend, divisor):     
        """
        :type dividend: int
        :type divisor: int
        :rtype: int
        """
        sign = [1,-1][(dividend < 0) != (divisor < 0)]
        dividend, divisor = abs(dividend), abs(divisor)
        res = 0
        i = 0
        Q = divisor
        while dividend >= divisor:
            dividend = dividend - Q
            Q <<= 1
            res += (1 << i)
            i+=1
            if dividend < Q:
                Q = divisor
                i = 0

        if sign == -1:
            res = -res

        if res < -2**31 or res > 2**31 -1:
            return 2**31 - 1

        return res

所以我无法分析此解决方案的时间复杂度。我知道应该是O(log(something))。通常对于算法,当输入在每次迭代中除以 2 时,我们说它们是 O(log(n)),但这里我在每次迭代时将 divisor 乘以 2 Q&lt;&lt;= 1,因此在每一步中,我都会朝着解决方案迈出更大的一步。显然,如果dividend 与更大的divisor 相同,我的算法会更快。同样,dividend 越大,divisor 的运行时间就越慢。

我的猜测是,控制该算法运行时间的方程基本上是 O(dividend/divisor) (duh 那是除法)的形式,其中包含一些日志来说明我在每个步骤中将 Q 乘以 2 @987654334 @...我不知道到底是什么。

编辑:

当我第一次发布问题时,我发布的算法是下面的问题,Alain Merigot 的答案是基于该算法。上面的版本和上面的版本之间的区别是我从来没有让我的红利低于 0,从而导致更快的运行时间。

    def divide(dividend, divisor):
        """
        :type dividend: int
        :type divisor: int
        :rtype: int
        """
        sign = [1,-1][(dividend < 0) != (divisor < 0)]
        dividend, divisor = abs(dividend), abs(divisor)
        res = 0
        i = 0
        tmp_divisor = divisor
        while dividend >= divisor:
            old_dividend, old_res = dividend, res
            dividend = dividend - tmp_divisor
            tmp_divisor <<= 1
            res += (1 << i)
            i+=1
            if dividend < 0:
                dividend = old_dividend
                res = old_res
                tmp_divisor >>= 2
                i -= 2

        if sign == -1:
            res = -res

        if res < -2**31 or res > 2**31 -1:
            return 2**31 - 1

        return res

【问题讨论】:

  • 我们根据操作数的位数来考虑复杂度,并且我们知道每次迭代都会产生一点结果。最坏的情况是除数 = 1 并且被除数为 2^31+k。在这种情况下,有 32 (n) 次迭代,复杂度是线性的。
  • 这其实很有道理,你介意扩展一下这个想法,以便我能理解吗?谢谢

标签: python-3.x algorithm bitwise-operators division bit-shift


【解决方案1】:

在最坏的情况下,您的算法是 O(m^2),其中 m 是结果中的位数。就输入而言,它将是 O(log(dividend/divisor) ^ 2)。

要了解原因,请考虑您的循环的作用。设a=除数,b=除数。只要足够大,循环就会从 a 中减去 b、2b、4b、8b、...,然后一次又一次地重复此序列,直到 a&lt;b

可以等效地写成两个嵌套循环:

while dividend >= divisor:
    Q = divisor
    i = 0
    while Q <= dividend:
        dividend = dividend - Q
        Q <<= 1
        res += (1 << i)
        i+=1

对于外循环的每次迭代,内循环将执行较少的迭代,因为dividend 更小。在最坏的情况下,对于外循环的每次迭代,内循环只会少做一次迭代。当某些 n 的结果为 1+3+7+15+...+(2^n-1) 时,就会发生这种情况。在这种情况下,可以证明 n = O(log(result)),但内循环迭代的总次数是 O(n^2),即结果大小是二次方的。

为了提高结果大小的线性,首先计算Qi 的最大需要值。然后从那里向后工作,从i 中减去 1,并在每次迭代中向右移动Q。这保证了总共不超过 2n 次迭代。

【讨论】:

  • 在分析 tx 上定位...我喜欢你建议的声音,这听起来很聪明,所以基本上 n 次迭代以找到 Q 和 i 的最大值,然后在下降的过程中找到另一个 n .. . 稍后我会尝试再次实现它!
【解决方案2】:

最坏情况的复杂性很容易找到。

每次迭代都会产生一点结果,迭代次数等于商中的位数。

当divider=1时,quotient=dividend,在这种情况下,迭代次数等于被除数在前导(最高有效)1之后的位数。当divide=2^(n-1)时最大化+k,其中 n 是位数,k 是任意数字,例如 1≤k

第一次迭代后,divide=dividend-diviser(=dividend-1) and diviser=2^1

迭代m后,除数=2^m,除数=dividend-(1+2^1+..+2^(m-1))=dividend-(2^m-1)

当被除数 0 时,这发生在 m=n 时。

因此,最坏情况下的步数为 n,复杂度与被除数的位数成线性关系。

【讨论】:

  • “每次迭代都会产生一点结果” - 这不是真的,因为i 的值可以重置为 0,它会返回更新之前的位。
  • @interjay 对,但实际上,问题已被编辑,算法已被修改。我的回答是相对于算法的第一个版本,i 从未重置为 0。
  • i 的值在原始版本中有时也会降低,所以我的观点成立。我确实认为原始版本可能是线性的,但要证明这一点比您的回答所表明的要困难得多,因为“迭代次数等于商中的位数”是不正确的。
  • 对不起,我应该在编辑中提到它...我现在要修复它,所以两个版本都在那里。在意识到没有必要低于 0 后,我修复了我的算法。
  • 我刚刚阅读并理解了您的分析@AlainMerigot,我必须同意@interjay。在您的推理中,当您说“当股息 res 的同一位,但每次最高有效位都不会再次被触及,这直观地听起来像 O(n^2) 与 n如果除数 = 1(就像您的示例一样),则被除数中的位数 -> 或 O(n^2),结果中的 n 位数更普遍。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多