Algorytm do znalezienia których liczba na liście sumują się do pewnej liczby

głosy
20

Mam listę numerów. Mam też pewną sumę. Suma jest wykonany z kilku numerach od mojej liście (I może / nie może wiedzieć, ile liczb ma z). Czy istnieje szybki algorytm, aby uzyskać listę możliwych liczb? Napisany w Pythonie byłoby świetnie, ale dobre pseudo-kod użytkownika. (I nie można jeszcze przeczytać coś innego niż Python: P)

Przykład

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

UWAGA: wiem od algorytmu do znalezienia które z listy rozmiar n sumę na inny numer numery . (Ale nie mogę odczytać C # i jestem w stanie sprawdzić, czy to działa na moje potrzeby jestem na Linux i próbowałem za pomocą mono, ale pojawiają się błędy i nie mogę dowiedzieć się, jak pracować C # :(
i ja znam algorytmu podsumować listę numerów dla wszystkich kombinacji (ale wydaje się być dość nieefektywne. nie muszę wszystkie kombinacje ).

Utwórz 06/08/2010 o 05:09
źródło użytkownik
W innych językach...                            


4 odpowiedzi

głosy
33

Problem sprowadza się do 0-1 Knapsack problem , gdzie starają się znaleźć zestaw z dokładną sumę. Rozwiązanie zależy od ograniczeń, w ogólnym przypadku problem ten jest NP-zupełny.

Jednak jeśli maksymalna suma wyszukiwania (nazwijmy to S) nie jest zbyt wysoka, to może rozwiązać problem za pomocą programowania dynamicznego. Ja to wyjaśnić za pomocą funkcji rekurencyjnej i memoization , która jest łatwiejsza do zrozumienia niż podejściu oddolnym.

Niech Kod funkcji f(v, i, S), tak, że zwraca liczbę podzbiorów w v[i:]który podsumowuje dokładnie S. Aby go rozwiązać rekurencyjnie, najpierw musimy przeanalizować podstawy (tj: v[i:]jest pusta):

  • S == 0: tylko podzbiór []ma sumę 0, więc jest to ważny podzbiór. W związku z tym, funkcja powinna powrócić 1.

  • S = 0: Jako jedyny podzbiór []ma sumę 0, nie jest ważny podzbiór. Z tego powodu funkcja powinna zwrócić 0.

Następnie przeanalizujmy rekurencyjnej sprawy (czyli: v[i:]nie jest pusta). Istnieją dwie możliwości: podać numer v[i]w bieżącym podzbiorze, lub nie zawierają go. Jeśli uwzględnimy v[i], następnie szukamy podzbiory, które mają sumę S - v[i], w przeciwnym razie, jesteśmy wciąż szuka podzbiorów z sumy S. Funkcja fmoże być realizowany w następujący sposób:

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))

Sprawdzając f(v, 0, S) > 0, można wiedzieć, czy istnieje rozwiązanie problemu. Niemniej jednak ta jest zbyt mała, każde wywołanie rekurencyjne ikra dwóch nowych połączeń, które prowadzi do O ^ n (2) algorytmu. Teraz możemy zastosować memoization , aby go uruchomić w czasie O (n * S), która jest szybsza, jeśli Snie jest zbyt duży:

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))

Teraz możliwe jest kod funkcji g, która zwraca jedną podgrupę, która sumuje S. Aby to zrobić, wystarczy dodać elementy tylko wtedy, gdy istnieje co najmniej jedno rozwiązanie łącznie z nich:

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))

Uwaga: To rozwiązanie mówi istnieją dwa podzbiory [10, 10], który podsumowuje 10. To dlatego, że zakłada, że ​​pierwsze dziesięć różni się od drugiej dziesiątce. Algorytm może być przymocowana do Zakładamy, że oba są równe dziesiątki (a tym samym odpowiedzieć na jedno), ale to jest nieco bardziej skomplikowana.

Odpowiedział 06/08/2010 o 06:16
źródło użytkownik

głosy
1

Więc logiczne jest, aby odwrócić porządek liczb, i załóżmy listę liczb l i suma zostać utworzony s .

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

Następnie przechodzimy tej pętli i numer jest wybrany z L w porządku i niech mówią, że to ja . istnieją 2 możliwe przypadki, albo ja to część sumy, czy nie. Więc zakładamy, że ja jest częścią rozwiązania, a wtedy problem sprowadza się do l samopoczucia l[l.index(i+1):]i ów byt Si więc, jeśli nasza funkcja jest (l, s), to nazywamy a(l[l.index(i+1):] ,s-i). i jeśli ja nie jest częścią s to musimy tworzyć s z l[l.index(i+1):]listy. Więc to jest podobne w obu przypadkach, jedyną zmianą jest jeśli jest częścią s, następnie s = si a inaczej s = s tylko.

teraz, aby zmniejszyć problem taki, że w liczbach przypadków wl przewyższają s my je usunąć w celu zmniejszenia złożoności aż l jest pusta iw tym przypadku numery, które zostały wybrane nie są częścią naszego rozwiązania, a my return false.

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

i w przypadku, L ma tylko jeden pierwiastek w lewo, a następnie każda z nich może być częścią s następnie powrócić prawdziwe, czy nie jest wtedy powrót fałszywy i uchwyt przechodzą przez inną liczbę.

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

Uwaga: w pętli, jeśli zostały wykorzystane b..but B znajduje się lista only.and i mają zaokrąglone wszędzie tam, gdzie jest to możliwe, tak, że nie powinniśmy dostać odpowiedź z powodu złego obliczeń zmiennoprzecinkowych punkt w Pythonie.

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)

to rozwiązanie działa fast.more szybko niż jeden wyjaśniono powyżej. Jednak to działa tylko dla liczb dodatnich. Jednak również działa dobrze, jeśli nie jest to rozwiązanie tylko inaczej zajmuje dużo czasu, aby wyjść z pętli.

przykładem jest prowadzony tak powiedzmy

    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

po prostu dać porównanie który wpadłem na moim komputerze, który nie jest tak dobry. za pomocą

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]

i

e = 2000

moja pętla pobiegł 1018 razy i 31 ms.

a poprzedni kod pętli prowadził 3415587 razy i zabrał gdzieś blisko 16 sekund.

Jednak w przypadku, gdy rozwiązanie nie istnieje mój kod prowadził więcej niż kilka minut, więc przestałem go i poprzedni kod pobiegł tylko w pobliżu około 17 ms i poprzedni kod działa z liczbami ujemnymi również.

więc rzeczą pewną poprawę można zrobić.

Odpowiedział 24/12/2015 o 19:29
źródło użytkownik

głosy
0
#!/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]

Ten kod Pythona zrobić co pytasz, to wydrukować unikalną parę liczb, których suma jest równa zmiennej docelowej.

jeśli numer docelowy jest 8, to wydrukować: 
1 7
2 6
3 5
3 5
5 3
6 2
9 -1
5 3
Odpowiedział 08/02/2017 o 04:38
źródło użytkownik

głosy
0

Znalazłem odpowiedź, która ma run-time złożoność O (n) i przestrzeń o złożoności O (2n), gdzie n oznacza długość listy.

Odpowiedź spełnia następujące ograniczenia:

  1. Lista może zawierać duplikatów, np [1,1,1,2,3] i chcesz znaleźć pary sumują się do 2

  2. Lista może zawierać zarówno pozytywne, jak i negatywne całkowitymi

Kod jest poniżej, a następnie wyjaśnienie:

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. Utwórz pusty słownik, iterację listy i umieścić wszystkie możliwe klucze w dict o wartości początkowej 0. Należy pamiętać, że kluczem (k-iter1) konieczne jest określenie, na przykład, jeśli lista zawiera 1, ale nie zawiera 4, oraz suma wynosi 5. następnie, kiedy patrzymy na 1, chcielibyśmy dowiedzieć ilu 4 mamy, ale jeśli 4 nie jest w dict, to będzie podnieść błąd.
  2. Iterację listy znowu, i policzyć, ile razy każda liczba całkowita, która występuje i zapisać wyniki do dict.
  3. Iterację przez dict, tym razem jest znalezienie ile par mamy. Musimy rozważyć 3 warunki:

    3.1 Kluczem jest tylko połowa sumy i ten klucz występuje więcej niż jeden raz na liście, na przykład lista jest [1,1,1], suma wynosi 2. Traktujemy tego warunku szczególnego co kod robi.

    3.2 Kluczem jest tylko połowa sumy i ten przycisk pojawia się tylko raz na liście, możemy pominąć ten warunek.

    3.3 W innych przypadkach, że klucz nie stanowi połowę sumy, wystarczy pomnożyć wartość z wartością inny klucz, gdzie te dwa klawisze sumują się do podanej wartości. Jeśli np suma wynosi 6, mnożymy temp [1] i temp [5], temp [2], a temp [4], etc ... (ja nie wymienione przypadki, w których numery są negatywne, ale idea jest taka sama. )

Najbardziej skomplikowane krokiem jest krok 3, która polega na przeszukiwaniu słownika, ale jak przeszukiwanie słownika jest zazwyczaj szybki, prawie stała złożoność. (Chociaż najgorszy przypadek jest O (n), ale nie powinno się zdarzyć na klucze całkowitych.) Tak więc, przy założeniu, że wyszukiwanie jest stała złożoność, całkowita złożoność wynosi O (n) jak tylko iteracyjne wymienić wiele razy oddzielnie.

Porady dla lepszego rozwiązania jest mile widziane :)

Odpowiedział 08/10/2017 o 09:30
źródło użytkownik

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more