查找总和为值的最小元素数(Python)

时间:2017-10-01 22:53:15

标签: python modular-arithmetic

我有一组正整数

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

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

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

我目前的方法是逐步嵌套循环,以生成一个点之前的所有可能答案,然后找到眼睛所需的最小数量的值。我使用quicksort作为单独的函数来避免重复的答案。

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]

澄清一下,代码有效;它看似凌乱而且不必要地彻底。

2 个答案:

答案 0 :(得分:0)

关键是使用内置combinations_with_replacement()库中的itertools。您可以将其用于您选择的任意数量的“组合”。这是我的代码,它打印您的示例,并且更为一般。 (请注意,您的上一个示例包含目标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成员的multiset中,将总和为0到{{1}的任何给定值}}。值240是唯一需要四个的值:所有其他值最多需要三个。

答案 1 :(得分:0)

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

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(值)中的多项式!我们通过在_cache的键上进行内部循环来节省了很多,因为它不能超过25个值,因此它是一个恒定的复杂循环!并且由于max_length不能大于len(值)(易于证明),即使你有一些非常复杂的值,总复杂度也不会超过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] 

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