加权字符串的组合:用sum(整数)计算整数组合的数量= m

时间:2018-06-04 10:25:20

标签: python dynamic-programming

给出一个整数列表,例如

l = [3, 5, 8, 13]

和一个整数,例如

m = 13

计算sum = m的整数组合的数量,例如

combinations = len([[8, 5], [5, 8], [13], [3, 5, 5], [5, 5, 3], [3, 5, 3], ...])

对于m的小值,我可以使用这种递归(类斐波纳契线性递归关系):

def RecursiveCount(m, integers):
    count = 0
    if m < 0:
        return 0
    if m == 0:
        return 1
    for i in integers:
        count += RecursiveCount(m-i, integers)
    return count

但是对于较大的l和m,它会变慢并建议使用动态编程来记忆已经解决的组合以减少递归调用。不幸的是,我无法实现这一点。我试过读这个,但没有帮助https://bio.informatik.uni-jena.de/wp/wp-content/uploads/2014/09/book_handout_3.pdf

编辑:如果我能够使用动态编程实现它,那么学习成果将是最好的

3 个答案:

答案 0 :(得分:3)

您可以通过将@functools.lru_cache装饰器添加到递归函数来轻松添加记忆。

li

这会自动缓存某些参数的结果,并在再次调用该函数之前先检查该缓存,从而大大减少调用次数,从而减少运行时间。但是,这要求所有参数都是可清除的,即您必须将@functools.lru_cache() def RecursiveCount(m, integers): ... 作为integers传递。

tuple的示例:结果:518,145;没有记忆的函数调用:4,672,513;记忆:29。

(如果这是用于DP的练习,这可能不是一个选项,但在实践中这很有效。)

答案 1 :(得分:2)

这是一个简单的搜索时间O(n * k),其中n是总和,k是列表中的整数数。搜索空间可以限制在O(max(list)),但为方便起见,我们只能使用O(n)

Python代码:

def f(n, nums):
  m = [0 for i in range(n + 1)]

  for s in range(min(nums), n + 1):
    for c in nums:
      if c == s:
        m[s] += 1
      if c < s:
        m[s] += m[s - c]

  return m[n]

Output

nums = (3, 5, 8, 13, 15, 20)
m = 200

t0 = time.time()
x = num_seq_sum(m, nums) # jdehesa's code
t1 = time.time()

print(x, t1-t0) # 233354368688517335733 4.085544586181641

t0 = time.time()
x = f(m, nums)
t1 = time.time()
print(x, t1-t0) # 233354368688517335733 0.0004315376281738281

t0 = time.time()
x = RecursiveCount(m, nums) # using @functools.lru_cache()
t1 = time.time()
print(x, t1-t0) # 233354368688517335733 0.0006241798400878906

答案 2 :(得分:1)

此解决方案不使用动态编程,但速度要快得多:

import math
from functools import reduce

def num_seq_sum(m, nums):
    if m == 0:
        return 1
    # Avoid repeated numbers
    nums = list(set(nums))
    # Begin with no numbers picked
    base = [0] * len(nums)
    return sum(_seqs_sum_rec(m, nums, base, 0))

def _seqs_sum_rec(m, nums, current, i):
    if i >= len(nums):
        raise StopIteration
    # Try without adding current number, add next numbers
    yield from _seqs_sum_rec(m, nums, current, i + 1)
    # Try adding current number
    n = nums[i]
    # While we can fit more copies of current number
    while m > n:
        current[i] += 1
        m -= n
        yield from _seqs_sum_rec(m, nums, current, i + 1)
    # If we can fit exactly one more
    if m == n:
        current[i] += 1
        # Number of permutations of the current combination
        yield _num_permutations(current)
    # Undo additions for parent call
    current[i] = 0

def _num_permutations(comb):
    return math.factorial(sum(comb)) // reduce(lambda a, b: a * b, (math.factorial(c) for c in comb), 1)

nums = [3, 5, 8, 13]
m = 13

print(RecursiveCount(m, nums) == num_seq_sum(m, nums))
# True

小型性能测试:

nums = [1, 3, 5, 8, 10, 15]
m = 30

%timeit RecursiveCount(m, nums)
# 1.48 s ± 6.86 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit num_seq_sum(m, nums)
# 4.77 ms ± 85.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)