Алгоритм, чтобы найти, какое число в списке сумма до определенного числа - PullRequest
23 голосов
/ 06 августа 2010

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

Пример

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

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

Ответы [ 4 ]

36 голосов
/ 06 августа 2010

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

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

Давайте закодируем функцию 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], в противном случае мы все еще ищем подмножества с суммой 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).Теперь мы можем применить памятка , чтобы запустить его во времени 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. Этопотому что предполагает, что первая десятка отличается от второй десятки.Алгоритм можно исправить, предполагая, что оба десятка равны (и, таким образом, отвечают на один), но это немного сложнее.

2 голосов
/ 24 декабря 2015

Итак, логика заключается в обратной сортировке чисел, и предположим, что список чисел равен 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 по порядку и допустим, что это i . Есть 2 возможных случая: либо i является частью суммы, либо нет. Итак, мы предполагаем, что i является частью решения, и тогда проблема сводится к l , равному l[l.index(i+1):], и s , равному si , поэтому , если наша функция - (l, s), то мы вызываем a(l[l.index(i+1):] ,s-i). и если 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..но 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 мс, а предыдущий код также работает с отрицательными числами.

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

0 голосов
/ 08 октября 2017

Я нашел ответ, который имеет сложность 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. Снова итерируем по списку и посчитаем, сколько разкаждое целое число встречается и сохраняет результаты в dict.
  3. Итерация thНа этот раз, чтобы понять, сколько у нас пар.Нам нужно учесть 3 условия:

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

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

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

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

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

0 голосов
/ 08 февраля 2017
#!/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
...