Алгоритм поиска, какое число в списке суммируется до определенного числа

У меня есть список чисел. У меня тоже есть определенная сумма. Сумма сделана из нескольких чисел из моего списка (я могу/не могу знать, из скольких чисел она сделана). Есть ли быстрый алгоритм для получения списка возможных чисел? Написано на Python было бы здорово, но псевдокод тоже хорош. (Я пока не могу читать ничего, кроме Python: P )

пример

list = [1,2,3,10]
sum = 12
result = [2,10]

Примечание: Я знаю алгоритм, чтобы найти, какие числа из списка размера N сумма другое число (но я не могу читать C#, и я не могу проверить, работает ли он для моих нужд. Я на Linux, и я пытался использовать Mono, но я получаю ошибки, и я не могу понять, как работать C# : (
и я знаю алгоритм суммирования списка чисел для всех комбинаций (но это, кажется, довольно неэффективно. Мне не нужны все комбинации.)

4 ответов


эта проблема сводится к 0-1 Ранце Проблема, где вы пытаетесь найти набор с точной суммой. Решение зависит от ограничений, в общем случае эта задача является NP-полной.

однако, если максимальная сумма поиска (назовем ее S) не слишком высока, то вы можете решить проблему с помощью динамического программирования. Я объясню это с помощью рекурсивной функции и memoization, что легче понять, чем подход "снизу вверх".

давайте закодируем функцию f(v, i, S), так что он возвращает количество подмножеств в v[i:] это означает именно S. Чтобы решить его рекурсивно, сначала мы должны проанализировать базу (т. е.:v[i:] пустой):

  • S == 0: единственное подмножество [] имеет сумму 0, поэтому это допустимое подмножество. Из-за этого, функция должна возвращать 1.

  • S != 0: как единственное подмножество [] имеет сумму 0, Нет допустимое подмножество. Из-за этого, функция должна возвращать 0.

тогда, давайте проанализируем рекурсивный случай (т. е.: v[i:] не пуст). Есть два варианта: включить число v[i] в текущем подмножестве, или не включать его. Если включить v[i], тогда мы ищем подмножества, которые имеют сумму S - v[i], в противном случае мы все еще ищем подмножества с sum S. Функция f может быть реализовано в следующем путь:

def f(v, i, S):
  if i >= len(v): return 1 if S == 0 else 0
  count = f(v, i + 1, S)
  count += f(v, i + 1, S - v[i])
  return count

v = [1, 2, 3, 10]
sum = 12
print(f(v, 0, sum))

при проверке f(v, 0, S) > 0, вы можете знать, есть ли решение вашей проблемы. Однако этот код слишком медленный, каждый рекурсивный вызов порождает два новых вызова, что приводит к алгоритму O(2^n). Теперь мы можем применить memoization чтобы заставить его работать во времени O (n*S), Что быстрее, если S - это не слишком большой:

def f(v, i, S, memo):
  if i >= len(v): return 1 if S == 0 else 0
  if (i, S) not in memo:  # <-- Check if value has not been calculated.
    count = f(v, i + 1, S, memo)
    count += f(v, i + 1, S - v[i], memo)
    memo[(i, S)] = count  # <-- Memoize calculated result.
  return memo[(i, S)]     # <-- Return memoized value.

v = [1, 2, 3, 10]
sum = 12
memo = dict()
print(f(v, 0, sum, memo))

теперь можно закодировать функцию g это возвращает одно подмножество, которое суммирует S. Для этого достаточно добавить элементы, только если есть хотя бы одно решение, включая их:

def f(v, i, S, memo):
  # ... same as before ...

def g(v, S, memo):
  subset = []
  for i, x in enumerate(v):
    # Check if there is still a solution if we include v[i]
    if f(v, i + 1, S - x, memo) > 0:
      subset.append(x)
      S -= x
  return subset

v = [1, 2, 3, 10]
sum = 12
memo = dict()
if f(v, 0, sum, memo) == 0: print("There are no valid subsets.")
else: print(g(v, sum, memo))

отказ от ответственности: это решение говорит, что есть два подмножества [10, 10], которые суммируют 10. Это потому, что он предполагает, что первая десятка отличается от второй десятки. Алгоритм можно исправить, предположив, что оба десятка равны (и, следовательно, отвечают на один), но это немного сложнее.


Итак, логика заключается в обратном сортировке чисел, и предположим, что список чисел l и сумма должна быть сформирована s.

   for i in b:
            if(a(round(n-i,2),b[b.index(i)+1:])):
                r.append(i)    
                return True
        return False

затем мы проходим через этот цикл, и номер выбирается из l в порядке и Пусть говорят, что это я . есть 2 варианта либо я часть суммы или нет. Итак, мы предполагаем, что я является частью решения, а затем проблема сводится к l будучи l[l.index(i+1):] и s будучи s-i Итак, если наша функция a (l,s), то мы вызываем a(l[l.index(i+1):] ,s-i). и если я не входит s тогда мы должны сформировать s С l[l.index(i+1):] список. Таким образом , это похоже в обоих случаях, только изменение, если i является частью s, тогда s=s-i и в противном случае S=S только.

теперь, чтобы уменьшить проблему так, что в случае, если числа в l больше, чем s, мы удаляем их, чтобы уменьшить сложность до L пуста, и в этом случае выбранные числа не являются частью нашего решения, и мы возвращаем false.

if(len(b)==0):
    return False    
while(b[0]>n):
    b.remove(b[0])
    if(len(b)==0):
        return False    

и в случае, если l имеет только 1 элемент слева, то либо он может быть частью s, то мы возвращаем true или нет, то мы возвращаем false и цикл будет проходить через другое число.

if(b[0]==n):
    r.append(b[0])
    return True
if(len(b)==1):
    return False

примечание в цикле, если используется b..но в нашем списке.и я округляю везде, где это возможно, так что мы не должны получить неправильный ответ из-за вычисления с плавающей запятой в python.

r=[]
list_of_numbers=[61.12,13.11,100.12,12.32,200,60.00,145.34,14.22,100.21,14.77,214.35,200.32,65.43,0.49,132.13,143.21,156.34,11.32,12.34,15.67,17.89,21.23,14.21,12,122,134]
list_of_numbers=sorted(list_of_numbers)
list_of_numbers.reverse()
sum_to_be_formed=401.54
def a(n,b):
    global r
    if(len(b)==0):
        return False    
    while(b[0]>n):
        b.remove(b[0])
        if(len(b)==0):
            return False    
    if(b[0]==n):
        r.append(b[0])
        return True
    if(len(b)==1):
        return False
    for i in b:
        if(a(round(n-i,2),b[b.index(i)+1:])):
            r.append(i)    
            return True
    return False
if(a(sum_to_be_formed,list_of_numbers)):
    print(r)

это решение работает быстро.более быстрый, чем тот, что описан выше. Однако это работает только для положительных чисел. Однако также он работает хорошо, если есть решение только в противном случае требуется много времени, чтобы выйти из циклов.

пример запуска похож на это, скажем

    l=[1,6,7,8,10]

and s=22 i.e. s=1+6+7+8
so it goes through like this 

1.) [10, 8, 7, 6, 1] 22
i.e. 10  is selected to be part of 22..so s=22-10=12 and l=l.remove(10)
2.) [8, 7, 6, 1] 12
i.e. 8  is selected to be part of 12..so s=12-8=4 and l=l.remove(8)
3.) [7, 6, 1] 4  
now 7,6 are removed and 1!=4 so it will return false for this execution where 8 is selected.
4.)[6, 1] 5
i.e. 7  is selected to be part of 12..so s=12-7=5 and l=l.remove(7)
now 6 are removed and 1!=5 so it will return false for this execution where 7 is selected.
5.)[1] 6
i.e. 6  is selected to be part of 12..so s=12-6=6 and l=l.remove(6)
now 1!=6 so it will return false for this execution where 6 is selected.
6.)[] 11
i.e. 1 is selected to be part of 12..so s=12-1=1 and l=l.remove(1)
now l is empty so all the cases for which 10 was a part of s are false and so 10 is not a part of s and we now start with 8 and same cases follow.
7.)[7, 6, 1] 14
8.)[6, 1] 7
9.)[1] 1

просто чтобы дать сравнение, которое я запустил на своем компьютере, что не так хорошо. с помощью

l=[61.12,13.11,100.12,12.32,200,60.00,145.34,14.22,100.21,14.77,214.35,145.21,123.56,11.90,200.32,65.43,0.49,132.13,143.21,156.34,11.32,12.34,15.67,17.89,21.23,14.21,12,122,134]

и

s=2000

мой цикл пробежал 1018 раз и 31 МС.

и предыдущий цикл кода пробежал 3415587 раз и занял около 16 секунд.

однако, если решение не существует, мой код работал более нескольких минут, поэтому я остановил его, а предыдущий код работал только около 17 мс, и предыдущий код также работает с отрицательными числами.

так что я вещь некоторые улучшения могут быть сделанный.


#!/usr/bin/python2

ylist = [1, 2, 3, 4, 5, 6, 7, 9, 2, 5, 3, -1]
print ylist 
target = int(raw_input("enter the target number")) 
for i in xrange(len(ylist)):
    sno = target-ylist[i]
    for j in xrange(i+1, len(ylist)):
        if ylist[j] == sno:
            print ylist[i], ylist[j]

этот код python делает то, что вы просили, он напечатает уникальную пару чисел, сумма которых равна целевой переменной.

if target number is 8, it will print: 
1 7
2 6
3 5
3 5
5 3
6 2
9 -1
5 3

Я нашел ответ, который имеет сложность времени выполнения O(n) и сложность пространства около O (2n), где n-длина списка.

ответ удовлетворяет следующим ограничениям:

  1. список может содержать дубликаты, например [1,1,1,2,3], и вы хотите найти сумму пар до 2

  2. список может содержать как положительные, так и отрицательные целые числа

код, как показано ниже, а затем объяснение:

def countPairs(k, a):
    # List a, sum is k
    temp = dict()
    count = 0
    for iter1 in a:
        temp[iter1] = 0
        temp[k-iter1] = 0
    for iter2 in a:
        temp[iter2] += 1
    for iter3 in list(temp.keys()):
        if iter3 == k / 2 and temp[iter3] > 1:
            count += temp[iter3] * (temp[k-iter3] - 1) / 2
        elif iter3 == k / 2 and temp[iter3] <= 1:
            continue
        else:
            count += temp[iter3] * temp[k-iter3] / 2
    return int(count)
  1. создайте пустой словарь, повторите список и поместите все возможные ключи в dict с начальным значением 0. Обратите внимание, что ключ (k-iter1) необходимо указать, например, если список содержит 1, но не содержит 4, а сумма равна 5. Затем, когда мы смотрим на 1, мы хотели бы найти сколько 4 у нас есть, А если 4 не в словаре, то это вызовет ошибку.
  2. повторите список еще раз и подсчитайте, сколько раз происходит каждое целое число и сохраните результаты в дикт.
  3. повторите через дикт, на этот раз, чтобы найти, сколько пар у нас есть. Необходимо рассмотреть 3 условия:

    3.1 ключ составляет только половину суммы, и этот ключ встречается более одного раза в списке, например list is [1,1,1], sum is 2. Мы рассматриваем это особое условие как то, что делает код.

    3.2 ключ только половину суммы, и этот ключ встречается только один раз в списке, то пропустите этот состояние.

    3.3 для других случаев, когда ключ не является половиной суммы, просто умножьте его значение на значение другого ключа, где эти два ключа суммируются с заданным значением. Например. Если сумма равна 6, мы умножаем temp[1] и temp[5], temp[2] и temp[4] и т. д... (Я не перечислял случаи, когда числа отрицательны, но идея та же.)

самым сложным шагом является Шаг 3, который включает в себя поиск словаря, но так как поиск словаря обычно быстрый, почти постоянная сложность. (Хотя в худшем случае O (n), но не должно произойти для целочисленных ключей.) Таким образом, при условии, что поиск является постоянной сложностью, общая сложность равна O(n), поскольку мы только повторяем список много раз отдельно.

совет для лучшего решения приветствуется:)