Python 为什么我的算法超时了?
下面是问题(来自Leetcode): 以下是我的解决方案:Python 为什么我的算法超时了?,python,dynamic-programming,memoization,Python,Dynamic Programming,Memoization,下面是问题(来自Leetcode): 以下是我的解决方案: memo = {} def lis_calc(lower_bound, offset): if memo.get((lower_bound, offset), None): return memo[(lower_bound, offset)] if offset >= len(nums): return 0 if nums[offset] > lower_bound:
memo = {}
def lis_calc(lower_bound, offset):
if memo.get((lower_bound, offset), None):
return memo[(lower_bound, offset)]
if offset >= len(nums):
return 0
if nums[offset] > lower_bound:
res = max(1 + lis_calc(nums[offset], offset + 1), lis_calc(lower_bound, offset + 1))
else:
res = lis_calc(lower_bound, offset + 1)
memo[(lower_bound, offset)] = res
return memo[(lower_bound, offset)]
在最坏的情况下(列表已经按升序排序),我们将有NxN唯一的函数调用(每对参数有N个值)。然而,我的算法对于非常大的输入是超时的,这表明我的算法没有O(NxN)的最坏情况时间开销。我在这里做错什么了吗?似乎是DP+记忆化的简单实现。它超时的测试输入是list(范围(12501))
我通过
lis\u calc(float('-inf'),0)
调用函数您的算法可能不是二次的,而是指数的
请看以下代码:
if nums[offset] > lower_bound:
res = max(1 + lis_calc(nums[offset], offset + 1), lis_calc(lower_bound, offset + 1))
在每一步,在最坏的情况下,你打两个电话。这两个电话中的每一个,在最坏的情况下,都会打两个电话。这四个电话中的每一个都会打两个电话,以此类推
如果两件事中有一件是真的,那么您的算法仍然可以是多项式的:
- 如果这些新呼叫中至少有一半被缓存,或者
- 如果保证最坏情况将在最坏
步骤中减少到下限情况(变为线性)log N
O(2**N)
步骤。这就是为什么它太慢了
或者……也许这不是真的,也许这只是需要一个额外的常数因子的二次时间,2500正好接近他们期望您的代码可以轻松工作的边缘,而您只是没有完全通过
每次加倍调用时,您不会缓存其中的一半,但应该缓存其中的一半
N-1
。因此,如果一切正常,你的总步骤应该是N*(N+1)+1
,但如果你的步骤稍有错误,则可能会偏离4倍……尽管确实如此,我认为,如果在他们测试的最大数字上,常数因子4足以产生差异,那么这不是一个很好的测试。在每一步中,你都要进行两次递归调用,除非你低于下限。它们有不同的参数,所以备忘录缓存不会有帮助。这两个电话中的每一个都会打两个电话,每一个都会打两个电话,依此类推。因此,除非你有证据证明在到达下限之前只能记录N个步骤,而不是N个步骤,否则你的代码是O(2**N)
,而不是O(N**2)
。事实上,现在我想起来了……你没有在每次加倍调用时缓存一半的调用,但是您应该缓存N
中N-1
的一半,因此,时间应该是N*(N+1)+1
,这毕竟是二次的。所以也许有一些小瑕疵可以修复?要测试这一点,请尝试添加呼叫数的计数。它是以N*(N+1)+1为界,还是简单地说,只是2*N**2
?并统计缓存命中数;它是否接近于N**2/2
?如果答案是肯定和肯定的,那么我的答案是错误的,但这很好;我可以删除它。:)请注意,您的代码正在执行显式递归。这与动态规划不同。DP通常使用数组来存储结果,这样您就不必每次需要时都重新计算它们。您应该阅读一些关于如何调试代码的提示。您可以使用这些提示来验证您的算法是否正确,或者找到不正确的地方。
if nums[offset] > lower_bound:
res = max(1 + lis_calc(nums[offset], offset + 1), lis_calc(lower_bound, offset + 1))