【每日一题】整数除法

在这篇题解中,我们将深入探讨如何在不使用乘法、除法和取余运算的情况下,实现两个整数的除法运算。这是一个非常有趣且具有挑战性的问题,它可以帮助我们加深对计算机算术运算和位运算的理解。

问题描述:

给定两个整数,被除数dividend和除数divisor。将两数相除,要求不使用乘法、除法和取余运算。 返回被除数dividend除以除数divisor得到的商。

解法一:循环减法(原始实现)

思路: 最直观的方法是使用循环减法。我们可以不断地从被除数中减去除数,直到被除数小于除数为止。减法的次数就是商。

步骤:

1.首先,我们需要确定商的符号。如果被除数和除数的符号不同,那么商应该是负数;否则,商应该是正数。2.然后,我们将被除数和除数都转为正数,以便进行后续的计算。3.接下来,我们开始循环减法的过程。我们不断地从被除数中减去除数,直到被除数小于除数为止。在这个过程中,我们用一个变量来记录减法的次数,这个次数就是商。4.最后,我们根据第一步确定的商的符号,来修正商的值。同时,我们还需要处理可能出现的溢出情况。

代码实现:

class Solution    def divide(self, dividend: int, divisor: int) -> int        # 确定商的符号        sign = 1        if (dividend > 0 and divisor < 0) or (dividend < 0 and divisor > 0):            sign = -1
# 将被除数和除数转为正数 dividend = abs(dividend) divisor = abs(divisor)
# 处理特殊情况 if divisor == 1 quotient = dividend else quotient = 0 while dividend >= divisor: dividend -= divisor quotient += 1
# 处理商的符号 quotient = sign * quotient
# 处理溢出情况 if quotient < -2**31 return -2**31 elif quotient > 2**31 - 1 return 2**31 - 1 else return quotient

复杂度分析:

时间复杂度:O(dividend/divisor)。在最坏情况下,我们需要将除数减去被除数/除数次。空间复杂度:O(1)。我们只使用了常数级别的额外空间。

然而,这个原始实现的时间复杂度较高,在被除数很大而除数很小的情况下,可能会导致超时。因此,我们需要考虑优化这个算法。

解法二:循环减法(二分优化)

思路: 我们可以使用二分查找的思想来优化循环减法的过程。与其每次只减去一个除数,不如一次减去多个除数,这样可以显著减少减法的次数。

步骤:

1.首先,我们依然需要确定商的符号,并将被除数和除数都转为正数。2.然后,我们开始循环减法的过程。在每一次循环中,我们都尝试减去除数的倍数,而不是只减去一个除数。我们从1倍的除数开始,如果被除数大于等于当前倍数的除数,我们就减去当前倍数的除数,并将当前倍数累加到商中。然后,我们将倍数翻倍,重复上述过程,直到当前倍数的除数大于被除数为止。接下来,我们继续处理剩下的部分,重复上述过程,直到被除数小于除数为止。3.最后,我们根据第一步确定的商的符号,来修正商的值,并处理可能出现的溢出情况。

代码实现:

class Solution    def divide(self, dividend: int, divisor: int) -> int        # 确定商的符号        sign = 1        if (dividend > 0 and divisor < 0) or (dividend < 0 and divisor > 0):            sign = -1
# 将被除数和除数转为正数 dividend = abs(dividend) divisor = abs(divisor)
quotient = 0 while dividend >= divisor: curr_divisor = divisor curr_quotient = 1 while dividend >= curr_divisor + curr_divisor: curr_divisor += curr_divisor curr_quotient += curr_quotient dividend -= curr_divisor quotient += curr_quotient
quotient = sign * quotient
if quotient < -2**31 return -2**31 elif quotient > 2**31 - 1 return 2**31 - 1 else return quotient

复杂度分析:

时间复杂度:O(log(dividend/divisor))。在每一次循环中,我们都将除数翻倍,因此循环的次数是O(log(dividend/divisor))。空间复杂度:O(1)。我们只使用了常数级别的额外空间。

下面是一个示意图,展示了二分优化的循环减法过程:


解法三:位运算+ 递归

思路: 我们可以使用位运算和递归的思想来进一步优化除法运算。我们知道,任何一个整数都可以表示为2的幂次之和。因此,我们可以将除数表示为2的幂次之和,然后在被除数中依次减去这些2的幂次的除数,直到被除数小于除数为止。

步骤:

1.首先,我们依然需要确定商的符号,并将被除数和除数都转为正数。2.然后,我们开始递归的过程。在每一次递归中,我们都将除数不断左移(相当于乘以2),直到它大于被除数为止。然后,我们从被除数中减去最大的那个小于等于被除数的2的幂次的除数,并将对应的2的幂次累加到商中。3.接下来,我们递归地处理剩下的部分,直到被除数小于除数为止。4.最后,我们根据第一步确定的商的符号,来修正商的值,并处理可能出现的溢出情况。

代码实现:

class Solution    def divide(self, dividend: int, divisor: int) -> int        # 确定商的符号        sign = 1        if (dividend > 0 and divisor < 0) or (dividend < 0 and divisor > 0):            sign = -1
# 将被除数和除数转为正数 dividend = abs(dividend) divisor = abs(divisor)
def dfs(dividend, divisor): if dividend < divisor: return 0 curr_divisor = divisor multiple = 1 while dividend >= curr_divisor << 1 curr_divisor <<= 1 multiple <<= 1 return multiple + dfs(dividend - curr_divisor, divisor)
quotient = sign * dfs(dividend, divisor)
if quotient < -2**31 return -2**31 elif quotient > 2**31 - 1 return 2**31 - 1 else return quotient

复杂度分析:

时间复杂度:O(log(dividend/divisor))。在每一次递归中,我们都将除数乘以2,因此递归的深度是O(log(dividend/divisor))。空间复杂度:O(log(dividend/divisor))。递归的深度为O(log(dividend/divisor))。

解法四:位运算+ 迭代

思路: 在解法三中,我们使用递归的方式来实现除法运算。实际上,我们也可以使用迭代的方式来实现同样的逻辑。这可以帮助我们避免递归调用的开销。

步骤:

1.首先,我们依然需要确定商的符号,并将被除数和除数都转为正数。2.然后,我们开始迭代的过程。在每一次迭代中,我们都将除数不断左移(相当于乘以2),直到它大于被除数为止。然后,我们从被除数中减去最大的那个小于等于被除数的2的幂次的除数,并将对应的2的幂次累加到商中。3.接下来,我们继续处理剩下的部分,重复步骤2,直到被除数小于除数为止。4.最后,我们根据第一步确定的商的符号,来修正商的值,并处理可能出现的溢出情况。

代码实现:

class Solution    def divide(self, dividend: int, divisor: int) -> int        # 确定商的符号        sign = 1        if (dividend > 0 and divisor < 0) or (dividend < 0 and divisor > 0):            sign = -1
# 将被除数和除数转为正数 dividend = abs(dividend) divisor = abs(divisor)
quotient = 0 while dividend >= divisor: curr_divisor = divisor multiple = 1 while dividend >= curr_divisor << 1 curr_divisor <<= 1 multiple <<= 1 dividend -= curr_divisor quotient += multiple
quotient = sign * quotient
if quotient < -2**31 return -2**31 elif quotient > 2**31 - 1 return 2**31 - 1 else return quotient

复杂度分析:

时间复杂度:O(log(dividend/divisor))。在每一次迭代中,我们都将除数乘以2,因此迭代的次数是O(log(dividend/divisor))。空间复杂度:O(1)。我们只使用了常数级别的额外空间。

总结:

在这篇题解中,我们探讨了四种解决整数除法问题的方法:循环减法(原始实现)、循环减法(二分优化)、位运算+递归和位运算+迭代。这些方法都避免了使用乘法、除法和取余运算,而是利用了减法、位运算和二分查找的思想。

方法 时间复杂度 空间复杂度 优点 缺点
循环减法(原始实现) O(dividend/divisor) O(1) 思路直观,易于理解 时间复杂度高,可能超时
循环减法(二分优化) O(log(dividend/divisor)) O(1) 显著减少减法次数,提高效率 实现略微复杂
位运算+递归 O(log(dividend/divisor)) O(log(dividend/divisor)) 利用位运算优化,思路巧妙 递归调用有额外开销
位运算+迭代 O(log(dividend/divisor)) O(1) 避免递归开销,效率高 实现相对复杂

循环减法(原始实现)可能是最容易理解和实现的。它直观地模拟了除法的过程,通过反复减法来计算商。然而,它的效率较低,在面对大数据时可能会超时。

循环减法(二分优化)引入了二分查找的思想,通过每次减去除数的倍数,显著减少了减法的次数,提高了算法的效率。这种优化对于理解二分查找的应用也有帮助。

位运算+递归和位运算+迭代进一步利用了位运算的特性,通过将除数表示为2的幂次之和,来减少计算步骤。其中,递归版本的思路更加巧妙,但迭代版本避免了递归调用的开销,效率更高。


请使用浏览器的分享功能分享到微信等