我正在玩 Singpath Python练习题。并且遇到了一个简单的问题,询问以下内容:
Given an input of a list of numbers and a high number,
return the number of multiples
of each of those numbers that are less than the maximum number.
For this case the list will contain a maximum of 3 numbers
that are all relatively prime to each other.
我写了这个简单的程序,它运行得非常好:
"""
Given an input of a list of numbers and a high number,
return the number of multiples
of each of those numbers that are less than the maximum number.
For this case the list will contain a maximum of 3 numbers
that are all relatively prime to each other.
>>> countMultiples([3],30)
9
>>> countMultiples([3,5],100)
46
>>> countMultiples([3,5,7],30)
16
"""
def countMultiples(l, max):
j = []
for num in l:
i = 1
count = 0
while num * i < max:
if num * i not in j:
j.append(num * i)
i += 1
return len(j)
print countMultiples([3],30)
print countMultiples([3,5],100)
print countMultiples([3, 5, 7],30)
但是当我尝试在SingPath上运行时,它给了我这个错误
Your code took too long to return.
Your solution may be stuck in an infinite loop. Please try again.
有没有人遇到与Singpath相同的问题?
答案 0 :(得分:0)
我怀疑你得到的错误正是它所说的。对于测试程序为您的功能提供的一些输入,返回需要很长时间。我自己对singpath一无所知,所以我不知道到底有多长。但我想如果你使用最好的算法,他们会给你足够的时间来解决问题。
如果传入非常大的max
值,您可以亲眼看到代码很慢。尝试将10000
作为max
传递,您可能会等待一两分钟才能得到结果。
在这些情况下,您的代码速度很慢有几个原因。首先,您到目前为止已经找到了每个倍数的list
,并且您正在搜索列表以查看是否已经看到最新值。每次搜索都需要与列表长度成比例的时间,因此对于函数的整个运行,它需要二次时间(相对于结果值)。
使用set
代替list
,您可以对此进行大量改进。您可以测试对象是否处于set
in(摊销)常量时间。但是如果j
是一个集合,那么在添加之前,您实际上不需要测试值是否已经存在,因为set
无论如何都会忽略重复值。这意味着你只需要add
集合的值,而无需关心它是否已存在。
def countMultiples(l, max):
j = set() # use a set object, rather than a list
for num in l:
i = 1
count = 0
while num * i < max:
j.add(num*i) # add items to the set unconditionally
i += 1
return len(j) # duplicate values are ignored, and won't be counted
这比原始代码运行的速度要快很多,并且一百万或更多的max
值将在不太合理的时间内返回。但如果你尝试更大的价值(比如1亿或10亿),你最终还是会遇到麻烦。那是因为你的代码使用循环来查找所有的倍数,这需要线性时间(相对于结果值)。幸运的是,有一个更好的算法。
(如果你想自己想出更好的方法,你可能想在这里停止阅读。)
更好的方法是使用除法来查找可以将每个值相乘多少次以获得小于max
的值。严格小于num
的{{1}}的倍数为max
((max-1) // num
是因为我们不想计算-1
本身)。整数除法比做循环要快得多!
但是增加了复杂性。如果你除以找到倍数的数量,你实际上并没有像我们上面那样放入max
的倍数。这意味着任何超过一个输入数的倍数的整数将被计算多次。
幸运的是,有一个很好的方法来解决这个问题。我们只需计算计算了多少整数,然后从总数中减去这些整数。当我们有两个输入值时,我们将重复计算每个整数,它是最小公倍数的倍数(因为我们保证它们是相对素数,意味着它们的产品)。
如果我们有三个值,我们可以为每对数字做相同的减法。但这也不完全正确。我们所有三个输入数字的倍数的整数将被计数三次,然后减去三次(因为它们是每对值的LCM的倍数)。因此,我们需要添加一个最终值,以确保所有三个值的倍数都包含在最终总和中一次。
set
对于import itertools
def countMultiples(numbers, max):
count = 0
for i, num in enumerate(numbers):
count += (max-1) // num # count multiples of num that are less than max
for a, b in itertools.combinations(numbers, 2):
count -= (max-1) // (a*b) # remove double counted numbers
if len(numbers) == 3:
a, b, c = numbers
count += (max-1) // (a*b*c) # add the vals that were removed too many times
return count
的任何值,这应该像常量时间一样运行。
现在,对于你给出的问题(可能总是不超过三个值),这可能和你需要的效率一样高。但是如果你想要一个适用于更多输入值的解决方案,你可以编写一个通用版本。它使用与先前版本相同的算法,并使用max
更多来一次获得不同数量的输入值。将奇数个数值的LCM的乘积数加到计数中,同时减去偶数个数的LCM的乘积数。
itertools.combinations
以下是此版本的示例输出,计算速度非常快:
import itertools
from functools import reduce
from operator import mul
def lcm(nums):
return reduce(mul, nums) # this is only correct if nums are all relatively prime
def countMultiples(numbers, max):
count = 0
for n in range(len(numbers)):
for nums in itertools.combinations(numbers, n+1):
count += (-1)**n * (max-1) // lcm(nums)
return count