【发布时间】:2018-08-02 02:52:12
【问题描述】:
这是问题(来自 Leetcode):
Given an unsorted array of integers, find the length of longest increasing subsequence.
Example:
Input: [10,9,2,5,3,7,101,18]
Output: 4
Explanation: The longest increasing subsequence is [2,3,7,101], therefore the length is 4.
Note:
There may be more than one LIS combination, it is only necessary for you to return the length.
Your algorithm should run in O(n2) complexity.
这是我的解决方案:
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 个唯一的函数调用(每个 arg 成对的 N 个值)。但是,我的算法对于非常大的输入会超时,这表明我的算法没有 O(NxN) 的最坏情况时间成本。我在这里做错了什么吗?似乎是 DP + memoization 的简单实现。它超时的测试输入是list(range(1,2501))
我通过lis_calc(float('-inf'), 0)调用函数
【问题讨论】:
-
在每个步骤中,您都会进行两次递归调用,除非您低于下限。它们有不同的参数,所以备忘录缓存无济于事。这两个电话中的每一个都打两个电话,每个电话打两个电话,依此类推。因此,除非您有一些证据证明这只能在到达下限之前记录 N 步,而不是 N 步,否则您的代码是
O(2**N),而不是O(N**2)。 -
实际上,现在我想起来了……你不会在每次加倍调用时缓存一半的调用,但你应该缓存
N-1的一半N,所以,时间应该是N * (N+1) + 1,毕竟是二次的。所以也许只有一些可以修复的小缺陷?要对此进行测试,请尝试仅添加调用次数的计数。它是否以N * (N+1) + 1为界——或者,为了简单起见,仅以2 * N**2为界?并统计缓存命中次数;离N**2 / 2很近吗? -
如果答案是肯定的,那么我的答案是错误的,但没关系;我只能删除它。 :)
-
请注意,您的代码正在执行显式递归。这与动态编程不相同。 DP 通常使用数组来存储结果,这样您就不必每次需要时都重新计算。
-
您应该阅读ericlippert.com/2014/03/05/how-to-debug-small-programs 了解有关如何调试代码的一些提示。您可以使用这些提示来验证您的算法是否正确,或者找出不正确的地方。
标签: python dynamic-programming memoization