组合总和的记忆时间与非记忆时间复杂度分析

时间:2018-08-12 00:11:48

标签: python algorithm dynamic-programming

我试图理解为什么使用lru_cache解决此问题会导致代码性能降低。

The question本质上是返回所有加起来等于某个目标的组合。

我正在使用lru_cache装饰器来做备忘录(docs),这是我的解决方案:

from functools import lru_cache

def combinationSum(candidates, target):
    return dfs(tuple(candidates), 0, target)

@lru_cache(maxsize=None)
def dfs(candidates, i, target):
    if target < 0:
        return []

    if target == 0:
        return [[]]

    if i == len(candidates):
        return []

    final_results = []
    for j in range(i, len(candidates)):

        results = dfs(candidates, j, target - candidates[j])

        for x in results:
            final_results.append([candidates[j]] + x)

    return final_results

似乎当lru_cache装饰器被注释掉时,该算法的运行速度提高了近50%。这似乎有点反常理,因为我认为应该减少解决方案的时间复杂度,即使增加从备忘录中检索结果的函数调用的开销也是如此。

对于备忘录式解决方案,我认为时间复杂度应为O(n^2*k*2^n),其中n是数组的长度,而k是{{1}范围内的所有数字}至0

这是我的分析(需要一些帮助进行验证):

target

我还缺少关于如何分析递归解决方案的时间复杂性的知识缺口,我可以在此方面提供一些帮助!

编辑:

我使用time complexity = possible states for memoization x work done at each step = (n * k) * (n * maximum size of results) = n * k * n * 2^n 作为测试输入,这是基准测试:

range(1, 10000)

2 个答案:

答案 0 :(得分:1)

您都没有给出两者参数,它们都很重要。通过选择 specific 对,可以使任何一个版本比另一个版本快得多。如果您以range(1, 10000)的形式传递candidates,则每个缓存查找必须(除其他事项外)进行9999比较,以确定候选对象始终相同-这是巨大的开销。尝试,例如

combinationSum(range(1, 1000), 45) # not ten thousand, just one thousand

用于缓存版本快得多的情况。之后:

>>> dfs.cache_info()
CacheInfo(hits=930864, misses=44956, maxsize=None, currsize=44956)
如果您不考虑进行高速缓存查找的费用,并且尝试高速缓存查找非常昂贵的情况,则

“分析”是没有用的。在O(1)的情况下可能会进行Dict查找,但是根据常数测试的昂贵程度,隐藏的常数因子可以任意大(对于包含N元素元组的键,建立相等至少需要N个比较)。

应该建议进行重大改进:将candidates排除在参数列表之外。它是不变的,因此实际上不需要传递它。然后,高速缓存仅需要存储快速比较的(i, target)对。

编辑:实际更改

这是candidates中未传递的另一版本代码。对于

combinationSum(range(1, 10000), 45)

在我的盒子上至少快50倍。还有其他重大变化:当target减小到零以下时,请勿进行递归调用。大量的缓存条目正在记录(j, negative_integer)自变量的空列表结果。在上述情况下,此更改将最终缓存的大小从449956减少到1036-并将命中次数从944​​4864减少到6853。

def combinationSum(candidates, target):

    @lru_cache(maxsize=None)
    def dfs(i, target):
        if target == 0:
            return [[]]
        assert target > 0
        if i == n:
            return []
        final_results = []
        for j in range(i, n):
            cand = candidates[j]
            if cand <= target:
                results = dfs(j, target - cand)
                for x in results:
                    final_results.append([cand] + x)
        return final_results

    n = len(candidates)
    result = dfs(0, target)
    print(dfs.cache_info())
    return result

答案 1 :(得分:0)

尝试对结果运行以下操作

>>>dfs.cache_info()

您应该得到类似这样的结果

CacheInfo(hits=2, misses=216, maxsize=None, currsize=216)

由于您的函数参数很长,因此它们通常与缓存的值不匹配,因此我将目标参数归咎于此,重组程序可能会大大提高匹配率。