引用自:LeetCode-中等-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
提示:
没什么好说的,是不可能的!这个题还是挺考察一部分对基本知识的掌握的。
根据题目要求,此题意思让我们手动模拟计算机除法。所以我们首先要了解结计算机世界中如何进行除法运算。
其实整体逻辑与我们平时手动计算很相似,只不过对于计算机而言,都是二进制数。
例如手动算18
除以7
的时候:
从被除数18
的最高位(左边)开始:
1
,用 1
除以 7
不够,只能商0
,余下1
因为1 - 7 * 0 = 1
8
,用18
(1 * 10 + 8 = 18
, 用上一位除完的余数乘以10
,再加上本位的数)除以7
,商2
,余下4
。0
,第二位是2
,所以就是0 * 10 + 2 = 2
。2
余:4
。2
的时候,不是要心算一下商1
余下11
,商2
余4
,这个判断过程不是要计算2 * 7
吗,这用到乘法就不符合题目要求了。但是我们仔细想一下,其实我们不需要用到乘法,因为在二进制情况下,其实要么就是商0
,意味着需要除的那个数小于除数,要么就商1
,意味着需要除的那个数大于等于除数,而不可能商2
,并且二进制里面根本也不会出现2
,如下面的二进制计算过程。对于二进制的话:
18
对应的二进制数为10010
7
对应的二进制数为111
从被除数10010
的最高位(左边)开始:
1
,用1
除以111
不够,商0
,所以1 - 111 * 0 = 1
,余下1
。0
,余数1 * 2 + 0 = 10
,用10
除以111
不够,商0
,所以10 - 111 * 0 = 10
,余下10
。0
,余数10 * 2 + 0 = 100
,用100
除以111
不够,商0
,所以100-111*0 = 100
,余下100
。1
,余数100 * 2 + 1 = 1001
,用1001
除以111
够,商1
,所以1001-111*1=10
,余下10
。0
,余数10*2 + 0 = 100
,用100
除以111
不够,商0
,所以100-111*0=100
,余下100
。100
,转为十进制就是4
,而商只需将每次计算的商合起来,我们5次计算的结果分别是'0'、'0'、'0'、'1'、'0'
,所以合起来的时候需要计算((((((0 * 2) + 0) * 2) + 0) * 2 + 1) * 2) + 0 = 00010 = 10
,我们又需要用到乘法了,怎么办呢。我们知道,在计算机中,本身数的存储方式就是以二进制的形式存储的,所以当我们想要把某个数乘2
的时候,只需要将这个数进行一次左移的位运算即可,如:十进制3乘以2结果为6,而十进制3在计算机中的存储形式为 11 (这里暂不考虑32为系统或64位系统),那么我们把 11 左移一位为 110,转为十进制也同样是 6,其实对于十进制数也是如此,如果我们想把十进制3乘以10, 那我们是不是也可以直接把 3 左移一位等到 3_,然后空位补上0,就得到30
。这样也就解决了合并商的需要用到乘法的问题。到这里基本上就大概清楚如何不用乘除法和mod运算来进行整数除法了,接下来只需要注意处理一下正负号,和整数的大小限制即可通过LeetCode。
除此之外,需要了解一下python中的bin()
函数,可以把十进制数转为二进制数的字符串形式,可能需要用到。
# 截断小数位
class Solution:
def divide(self, dividend: int, divisor: int) -> int:
# 判断除数和被除数的正负号 并确定结果的正负号
is_pos_1 = True if dividend > 0 else False
is_pos_2 = True if divisor > 0 else False
result_is_neg = True if is_pos_1 ^ is_pos_2 else False
# 将两个值都转换为正数,其中把被除数转换为二进制字符串'0bXXX',并去掉'0b'和负号
dividend_2_pos = bin(dividend).replace('0b', '').replace('-', '')
divisor_pos = -divisor if divisor < 0 else divisor
# 按两个正数计算除法
temp_divided = 0
result = 0
for number in dividend_2_pos:
temp_divided = (temp_divided << 1) + int(number, 2)
if temp_divided >= divisor_pos:
temp_divided -= divisor_pos
result = (result << 1) + 1
else:
result = (result << 1)
# 判断正负号,以及确定数值范围在[−2^31, 2^31 − 1]
result = -result if result_is_neg else result
result = -2147483648 if result < -2147483648 else result
result = 2147483647 if result > 2147483647 else result
return result
除此之外,这个题目要求的是让截断小数位,但是以下是一个四舍五入的版本的。
# 小数位四舍五入
class Solution1:
def divide(self, dividend: int, divisor: int) -> int:
# 判断除数和被除数的正负号 并确定结果的正负号
is_pos_1 = True if dividend > 0 else False
is_pos_2 = True if divisor > 0 else False
result_is_neg = True if is_pos_1 ^ is_pos_2 else False
# 将两个值都转换为正数,其中把被除数转换为二进制'0bXXX',并去掉'0b'和负号
dividend_2_pos = bin(dividend).replace('0b', '').replace('-', '')
divisor_pos = -divisor if divisor < 0 else divisor
# 按两个正数计算除法
temp_divided = 0
result = 0
for number in dividend_2_pos:
temp_divided = (temp_divided << 1) + int(number, 2)
if temp_divided >= divisor_pos:
temp_divided -= divisor_pos
result = (result << 1) + 1
else:
result = (result << 1)
# 我们可以发现, 在进行reslut 正负号判断之间进行向上或者向下取整可以 统一起来,否则需要分result的正负号分别讨论向上向下取整
# 判断是否向上取整 <我们可以把最后的余数乘以2, 然后与除数比较大小,
# 如果大于除数,说明余数是大于除数的1/2, 那么最后就应该向上取整,否则向下取整>
is_rounding_up = True if temp_divided << 1 >= divisor_pos else False
# 我们这里算到被除数的最后一位就停止了,所以result 本身就是一个整数
# 所以,若向上取整,那么reslut需要 +1,否则保持不变
result = result + 1 if is_rounding_up else result
# 判断正负号,以及确定数值范围在[−2^31, 2^31 − 1]
result = -result if result_is_neg else result
result = -2147483648 if result < -2147483648 else result
result = 2147483647 if result > 2147483647 else result
return result
class Solution:
def divide(self, dividend: int, divisor: int) -> int:
# 判断除数和被除数的正负号 并确定结果的正负号
is_pos_1 = True if dividend > 0 else False
is_pos_2 = True if divisor > 0 else False
result_is_neg = True if is_pos_1 ^ is_pos_2 else False
# 将两个值都转换为正数,其中把被除数转换为二进制字符串'0bXXX',并去掉'0b'和负号
dividend_2_pos = bin(dividend).replace('0b', '').replace('-', '')
divisor_pos = -divisor if divisor < 0 else divisor
# 按两个正数计算除法
temp_divided = 0
result = 0
for number in dividend_2_pos:
temp_divided = (temp_divided << 1) + int(number, 2)
if temp_divided >= divisor_pos:
temp_divided -= divisor_pos
result = (result << 1) + 1
else:
result = (result << 1)
# 判断正负号,以及确定数值范围在[−2^31, 2^31 − 1]
result = -result if result_is_neg else result
result = -2147483648 if result < -2147483648 else result
result = 2147483647 if result > 2147483647 else result
return result
class Solution1:
def divide(self, dividend: int, divisor: int) -> int:
# 判断除数和被除数的正负号 并确定结果的正负号
is_pos_1 = True if dividend > 0 else False
is_pos_2 = True if divisor > 0 else False
result_is_neg = True if is_pos_1 ^ is_pos_2 else False
# 将两个值都转换为正数,其中把被除数转换为二进制'0bXXX',并去掉'0b'和负号
dividend_2_pos = bin(dividend).replace('0b', '').replace('-', '')
divisor_pos = -divisor if divisor < 0 else divisor
# 按两个正数计算除法
temp_divided = 0
result = 0
for number in dividend_2_pos:
temp_divided = (temp_divided << 1) + int(number, 2)
if temp_divided >= divisor_pos:
temp_divided -= divisor_pos
result = (result << 1) + 1
else:
result = (result << 1)
# 我们可以发现, 在进行reslut 正负号判断之间进行向上或者向下取整可以 统一起来,否则需要分result的正负号分别讨论向上向下取整
# 判断是否向上取整 <我们可以把最后的余数乘以2, 然后与除数比较大小,
# 如果大于除数,说明余数是大于除数的1/2, 那么最后就应该向上取整,否则向下取整>
is_rounding_up = True if temp_divided << 1 >= divisor_pos else False
# 我们这里算到被除数的最后一位就停止了,所以result 本身就是一个整数
# 所以,若向上取整,那么reslut需要 +1,否则保持不变
result = result + 1 if is_rounding_up else result
# 判断正负号,以及确定数值范围在[−2^31, 2^31 − 1]
result = -result if result_is_neg else result
result = -2147483648 if result < -2147483648 else result
result = 2147483647 if result > 2147483647 else result
return result
if __name__ == "__main__":
so = Solution()
so1 = Solution1()
dividend = 11
divisor = 4
print('测试1: \ndividend = 11 divisor = 4')
print('小数位截断:dividend / divisor = ', so.divide(dividend, divisor))
print('四舍五入:dividend / divisor = ', so1.divide(dividend, divisor))
print()
dividend = 12
divisor = 5
print('测试2: \ndividend = 12 divisor = 5')
print('小数位截断:dividend / divisor = ', so.divide(dividend, divisor))
print('四舍五入:dividend / divisor = ', so1.divide(dividend, divisor))
很不错的一道题嘛,因为这个题还专门回顾了一下计算机的一些位运算,和怎么算减法(你还记得减法怎么算的吗,hia hia hia),温故而知新~!
并且又重新了解一下bin()、int(number, 2)
这两个好玩的函数!