【问题标题】:Finding the smallest number of elements that sum to a value (Python)查找总和为一个值的最少元素数(Python)
【发布时间】:2018-03-13 01:26:42
【问题描述】:

我有一组正整数

values = [15, 23, 6, 14, 16, 24, 7]

可以通过替换来选择,以求和为 0 到 24(含)之间的数字,其中使用的值越少越好。

例如,16 + 16 (mod 25) = 32 (mod 25) = 7 但 7 (mod 25) = 7 使用较少的加法,因此是首选。

我目前的方法是顺序越来越多地嵌套 for 循环以生成直到某个点的所有可能答案,然后找到眼睛所需的最少数量的值。我使用快速排序作为一个单独的函数来避免重复的答案。

answers = []
for i in values:
    if i % 25 == n:
        if [i] not in answers:
            answers.append([i])
if not answers:
    for i in values:
        for j in values:
            if (i + j) % 25 == n:
                check = quicksort([i, j])
                if check not in answers:
                    answers.append(check)
if not answers:
    for i in values:
        for j in values:
            for k in values:
                if (i + j + k) % 25 == n:
                    check = quicksort([i, j, k])
                    if check not in answers:                            
                        answers.append(check)
for i in answers:
    print(i)

然后是一个典型的输出

[14, 14]

从中我可以看出 [14, 14] 是最有效的总和。

我从暴力破解中知道,对 n 的所有可能选择求和最多需要四个值,但这似乎是一种非常乏味的寻找最有效总和的方法。有没有更优雅的算法?

编辑:额外的例子。

如果我们选择 n = 13,代码会吐出来

[15, 23]
[6, 7]
[14, 24]

并选择 n = 18 个输出

[14, 15, 15]
[6, 15, 23]
[23, 23, 23]
[7, 14, 23]
[6, 6, 7]
[6, 14, 24]
[14, 14, 16]

为了澄清,代码有效;它只是看起来凌乱且不必要的彻底。

【问题讨论】:

  • 您能否提供另一个解决方案的示例,以便清楚您的程序在做什么。
  • 我已经用更多示例更新了问题

标签: python modular-arithmetic


【解决方案1】:

关键是使用内置itertools库中的combinations_with_replacement()。您可以将其用于您选择的任意数量的“组合”。这是我的代码,它打印您的示例并且更通用。 (请注意,您的最后一个示例具有目标 19,但您将其输入错误为 18。)

from itertools import combinations_with_replacement

def print_modulo_sums(values, target, modulus, maxsize):
    """Print all multisets (sets with possible repetitions) of minimum
    cardinality from the given values that sum to the target,
    modulo the given modulus. If no such multiset with cardinality
    less than or equal the given max size exists, print nothing.
    """
    print("\nTarget = ", target)
    found_one = False
    for thissize in range(1, maxsize + 1):
        for multiset in combinations_with_replacement(values, thissize):
            if sum(multiset) % modulus == target:
                print(sorted(multiset))
                found_one = True
        if found_one:
            return

values = [15, 23, 6, 14, 16, 24, 7]

print_modulo_sums(values, 7, 25, 5)
print_modulo_sums(values, 3, 25, 5)
print_modulo_sums(values, 13, 25, 5)
print_modulo_sums(values, 19, 25, 5)

打印输出是:

Target =  7
[7]

Target =  3
[14, 14]

Target =  13
[15, 23]
[6, 7]
[14, 24]

Target =  19
[14, 15, 15]
[6, 15, 23]
[23, 23, 23]
[7, 14, 23]
[6, 6, 7]
[6, 14, 24]
[14, 14, 16]

在最后添加一个简单的循环可以确认,对于给定的一组值和给定的模数,在最多具有 4 成员的多重集中将求和从 024 的任何给定值。值 08 是唯一需要四个的值:所有其他值最多需要三个。

【讨论】:

  • 只要打败我就行了。如果你想要排序的答案,你可以从values开始排序,然后combinations_with_replacement将被排序。文档说,“组合按字典排序顺序发出。因此,如果输入的可迭代对象已排序,则组合元组将按排序顺序生成。”
  • @saulspatz:我想到了这一点,但这意味着要多写一行代码。我知道,这可能是错误的推理。我在每个打印行中排序,但不是在整个行中排序,因为这是给定示例所做的。
【解决方案2】:

首先,你可以将整个事情表达为一个很好的循环过程

def checker1(values, n, length):
  if length == 0:
    return False

  for value in values:
    if length == 1 and value % 25 == n:
      return [value]
    else:
      recurrent_call = checker(values, (n - value) % 25, length - 1)
      if recurrent_call:
        return [value] + recurrent_call

  return False 

它的复杂性与以前完全相同,但现在它是通用的,您只需在 max_length 从 1 开始的循环中运行它。现在在复杂性方面,您可以使用动态编程,请注意,一旦您遍历所有对,您可以更快地遍历三元组,只需在整个列表上迭代一次并检查您是否早先缓存了正确的总和。让我们去做吧。

def checker2(values, n, max_length):

  _cache = {value: [value] for value in values}

  for length in range(2, max_length+1):

    for value in values:
      for value_in_cache in _cache.keys():
        value_mod = (value_in_cache + value) % 25         
        if value_mod not in _cache:
           _cache[value_mod] = _cache[value_in_cache] + [value]

    if n in _cache:
      return _cache[n]

  return False

这将计算复杂性降低了一个数量级以上,因为我们永远不会重新计算我们已经知道的内容。预期的复杂性(假设从 python 中的字典读取现在是 O(1):

O(max_length * len(values))

在它是 len(values) 中的多项式之前!通过对 _cache 的键进行内部循环,我们节省了很多,它的值不能超过 25 个,因此 - 这是一个恒定的复杂性循环!而且由于 max_length 不能大于 len(values)(易于证明),因此总复杂度不能超过 O(len(values)^2),即使您有一些非常复杂的值集。

还有一个快速测试:

print(checker2(values, 23, 5))       # [23]
print(checker2(values, 13, 5))       # [23, 15]
print(checker2(values, 19, 5))       # [6, 15, 23] 

这些方法假设您只关心最短的解决方案,而不是所有解决方案。如果您关心所有解决方案,您仍然可以采用这种方式,将“缓存”的值列表存储到同一个存储桶,然后返回所有组合等,但这样您就不会节省太多计算量。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-12-31
    • 1970-01-01
    • 2011-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多