【问题标题】:Find all combinations of `n` positive numbers adding up to `k` in Python? [duplicate]在Python中找到所有n个正数加起来等于k的组合? [复制]
【发布时间】:2020-06-12 12:39:33
【问题描述】:

如何在 Python 中有效地找到 n 正整数的所有可能组合加上给定数字 k

我知道我可以通过过滤所有可能的组合来解决这个问题:

import itertools


def to_sum_k(n, k):
    for i in itertools.product(range(1, k - n + 2), repeat=n):
        if sum(i) == k:
            yield i


print(list(to_sum_k(3, 5)))
# [(1, 1, 3), (1, 2, 2), (1, 3, 1), (2, 1, 2), (2, 2, 1), (3, 1, 1)]

我看到有人以抽象的方式讨论过类似的事情here,但我没有看到将其转换为代码的简单方法。


另外,我更喜欢迭代解决方案而不是递归解决方案。

【问题讨论】:

  • 您期待什么样的答案?你的代码有什么问题?
  • 您可以通过仅找到 n-1 个数字的组合来使该代码更高效 - 然后计算最终数字必须是多少才能达到所需的总数(拒绝该数字为非正数的任何组合)。
  • 动态编程是要走的路。
  • @NicolasGervais 这完全没有效率。所有生成的元组中只有一小部分满足条件,并且随着输入的增加,情况会变得更糟。 list(to_sum_k(4, 100)) 需要约 15 秒才能生成约 150k 条目,在约 90m 中可见。
  • @NicolasGervais 除了你的解决方案缺少很多结果,而我的没有。

标签: python algorithm combinatorics


【解决方案1】:

基于this的递归解决方案:

def to_sum_k_rec(n, k):
    if n == 1:
        yield (k,)
    else:
        for x in range(1, k):
            for i in to_sum_k_rec(n - 1, k - x):
                yield (x,) + i


print(list(to_sum_k_rec(3, 5)))
# [(1, 1, 3), (1, 2, 2), (1, 3, 1), (2, 1, 2), (2, 2, 1), (3, 1, 1)]

还有一个迭代:

import itertools


def to_sum_k_iter(n, k):
    index = [0] * (n + 1)
    index[-1] = k
    for j in itertools.combinations(range(1, k), n - 1):
        index[1:-1] = j
        yield tuple(index[i + 1] - index[i] for i in range(n))


print(list(to_sum_k_iter(3, 5)))
# [(1, 1, 3), (1, 2, 2), (1, 3, 1), (2, 1, 2), (2, 2, 1), (3, 1, 1)]

在时间方面,递归解决方案似乎是最快的:

%timeit list(to_sum_k_OP(4, 100))
# 1 loop, best of 3: 13.9 s per loop
%timeit list(to_sum_k_rec(4, 100))
# 10 loops, best of 3: 101 ms per loop
%timeit list(to_sum_k_iter(4, 100))
# 1 loop, best of 3: 201 ms per loop

【讨论】:

    【解决方案2】:

    一种比 OP 更有效,但仍然基于过滤器(因此效率低于公认答案)的方法是使用:

    import itertools
    import flyingcircus as fc
    
    
    def to_sum_k(n, k):
        for i in itertools.combinations_with_replacement(range(1, k - n + 2), r=n):
            if sum(i) == k:
                yield from fc.unique_permutations(i)
    
    
    print(list(to_sum_k(3, 5)))
    # [(1, 1, 3), (1, 2, 2), (1, 3, 1), (2, 1, 2), (2, 2, 1), (3, 1, 1)]
    

    一些采样时间:

    %timeit list(to_sum_k_OP(4, 80))
    # 1 loop, best of 3: 5.43 s per loop
    %timeit list(to_sum_k(4, 80))
    # 1 loop, best of 3: 331 ms per loop
    

    免责声明:我是flyingcircus 包的主要作者)。

    【讨论】:

    • 我只是添加了这个以避免其他人建议这个,因为这不是我想要的。
    【解决方案3】:

    这是一个递归解决方案:

    def to_sum_k(n, k):
        if n == 1: 
            return [ [k] ]
        if n > k or n <= 0:
            return []
        res = []
        for i in range(k):
            sub_results = to_sum_k(n-1, k-i)
            for sub in sub_results:
                res.append(sub + [i])
        return res    
    
    to_sum_k(3, 5)
    

    结果:

    [[5, 0, 0],
     [4, 1, 0],
     [3, 2, 0],
     [2, 3, 0],
     [1, 4, 0],
     [4, 0, 1],
     [3, 1, 1],
     [2, 2, 1],
     [1, 3, 1],
     [3, 0, 2],
     ...
     [2, 1, 2],
    

    通过保留我们之前计算的所有结果的“缓存”,并在需要时重用它们,可以将相同的解决方案优化为半动态编程解决方案:

    cache = {}
    def to_sum_k(n, k):
        res = cache.get((n,k), [])
        if res: 
            return res
    
        if n == 1: 
            res  = [ [k] ]
        elif n > k or n <= 0:
            res = []
        else:
            for i in range(k):
                sub_results = to_sum_k(n-1, k-i)
                for sub in sub_results:
                    res.append(sub + [i])
        cache[(n,k)] = res
        return res    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-06-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-23
      • 2020-04-17
      相关资源
      最近更新 更多