动态编程 - 使用一组整数计算和的方法数

时间:2014-05-14 19:48:37

标签: python algorithm dynamic-programming

我经历了这个问题

Number of ways to add up to a sum S with N numbers Find all ways to sum given number (with repetitions allowed) from given set

那里的答案不太明白,

我写了两个方法来解决一个问题:

使用数字N(允许重复)找出可以达到和S的方法数

例如。 sum = 4和number = 1,2,3 answer是1111,22,1122,31,13,1212,2112,2212

在一种方法中我使用了memoization而在另一种方法中我没有使用memoization。在我的机器中,memoize版本的运行速度比非memoized版本慢

这两种解决方案都有效。

记事本版:

def find_denomination_combinations(amount, denominations):
    memo = {}

    def calculate_combinations(max_amt):
        return_list = list()

        for denomination in denominations:
            new_sum = max_amt - denomination
            if new_sum == 0:
                return_list.append([max_amt])
                return return_list
            elif new_sum < 0:
                return [[]]
            else:
                if new_sum in memo:
                    combi_list = memo[new_sum]
                else:
                    combi_list = calculate_combinations(new_sum)
                for combination in combi_list:
                    if new_sum in memo and combination[:] not in memo[new_sum]:
                        memo[new_sum].append(combination[:])
                    else:
                        memo[new_sum] = []
                        memo[new_sum].append(combination[:])
                    combination.append(denomination)
                    return_list.append(combination)
        return return_list

    result = calculate_combinations(amount)
    return result

非备忘版本

def find_denomination_combinations_nmemo(amount, denominations):

    def calculate_combinations(max_amt):
        return_list = list()

        for denomination in denominations:
            new_sum = max_amt - denomination
            if new_sum == 0:
                return_list.append([max_amt])
                return return_list
            elif new_sum < 0:
                return [[]]
            else:
                combi_list = calculate_combinations(new_sum)
                for combination in combi_list:
                    combination.append(denomination)
                    return_list.append(combination)
        return return_list

    result = calculate_combinations(amount)
    return result

我的算法是:

每个D的

[T(sum-D)],其中D属于给定的整数集

如果输入和= 16并且整数设置= [1,2,3]

非记忆版本在0.3秒内运行,记忆版本需要5秒

1 个答案:

答案 0 :(得分:1)

我认为memoized版本较慢,因为它用于更新最外层else块中的备忘录dict的复杂代码。它可以更简单:

if new_sum in memo:
    combi_list = memo[new_sum]
else:
    combi_list = memo[new_sum] = calculate_combinations(new_sum)
for combination in combi_list:
    return_list.append(combination + [denomination])

这要快得多。使用此修复程序,在大多数情况下,memoized版本应该比非memoized代码更快。

但是还有其他问题。如果您的denominations列表未按递增顺序排序或者面额值之间存在间隙,则会得到错误的结果。基本上,任何可能导致elif案件被击中的情况都会给出错误的结果。

这是for循环体的一个版本,用于纠正这些问题:

new_sum = max_amt - denomination
if new_sum == 0:
    return_list.append([max_amt]) # don't return here, to allow unsorted denominations!
elif new_sum > 0:
    if new_sum in memo:
        combi_list = memo[new_sum]
    else:
        combi_list = memo[new_sum] = calculate_combinations(new_sum)
    for combination in combi_list:
        return_list.append(combination + [denomination])
# do nothing for new_amt < 0

通过使每个调用在备忘录中保存自己的结果,而不是依赖于调用者来执行此操作,并将基本情况逻辑(对于new_sum == 0)与记忆化。我还重命名或删除了几个变量:

def find_denomination_combinations_blckknght(amount, denominations):
    memo = {0: [[]]} # prefill value for base case of calculate_combinations where amt==0

    def calculate_combinations(amt):
        if amt not in memo:
            memo[amt] = []
            for denomination in denominations:
                new_amt = amt - denomination
                if new_amt >= 0:
                    for combination in calculate_combinations(new_amt):
                        memo[amt].append(combination + [denomination])
                # do nothing for new_amt < 0
        return memo[amt]

    return calculate_combinations(amount)

这稍慢,可能是因为额外的函数调用,但代码更简单,在任何地方都没有elifelse个案例!