Combination Sum
Du erhältst eine Liste candidates verschiedener positiver Ganzzahlen und eine positive Ganzzahl target. Finde alle Kombinationen von Kandidaten, deren Werte sich genau zu target addieren, wobei jeder Kandidat beliebig oft verwendet werden darf. Zwei Kombinationen sind gleich, wenn sie dieselben Werte gleich oft enthalten; [2, 3, 3] und [3, 2, 3] zählen also als eine Kombination.
Gib jede Kombination mit ihren Werten in aufsteigender Reihenfolge und die Kombinationen in lexikografischer Reihenfolge zurück: Vergleiche zwei Kombinationen von links nach rechts Wert für Wert; die Kombination mit dem kleineren Wert an der ersten unterschiedlichen Stelle kommt zuerst.
Funktion
- candidatesinteger-array
- die verschiedenen Werte, die du in beliebiger Reihenfolge und beliebig oft verwenden kannst
- targetinteger
- die Gesamtsumme, die jede Kombination genau erreichen muss
- Gibt zurückinteger-2d-array
- jede Kombination, die die Zielsumme ergibt, jeweils aufsteigend sortiert und in lexikografischer Reihenfolge aufgeführt
Einschränkungen
1 ≤ candidates.length ≤ 502 ≤ candidates[i] ≤ 5002 ≤ target ≤ 500- Alle Werte in
candidatessind unterschiedlich und in keiner bestimmten Reihenfolge. - Mindestens eine Kombination erreicht
target, und höchstens 150 tun das.
Beispiele
- Eingabe
- candidates = [6, 2, 3]target = 8
- Ausgabe
- [[2, 2, 2, 2], [2, 3, 3], [2, 6]]
- Erklärung
- Vier 2en ergeben 8, ebenso wie 2 + 3 + 3 und 2 + 6. Alle drei beginnen mit 2, also bestimmt der zweite Wert die Reihenfolge: 2, dann 3, dann 6. Ohne eine 2 bleiben nur 3en und 6en, und jede Kombination davon ist ein Vielfaches von 3, was 8 nicht ist.
- Eingabe
- candidates = [5, 3, 4]target = 11
- Ausgabe
- [[3, 3, 5], [3, 4, 4]]
- Erklärung
- 3 + 3 + 5 und 3 + 4 + 4 ergeben beide 11. Sie stimmen beim ersten Wert überein, und beim zweiten ist die 3 kleiner als die 4, daher kommt
[3, 3, 5]zuerst. Keine Kombination nur aus 4ern und 5ern ergibt 11.
- Eingabe
- candidates = [4, 9]target = 9
- Ausgabe
- [[9]]
- Erklärung
- 9 allein ist eine Kombination. 4er ergeben auf ihrem Weg an der 9 vorbei nur 4, 8 und 12, und 4 + 9 ist bereits 13, also ist
[9]die einzige Antwort.
+12 versteckte Tests beim Einreichen
Weiterführende Frage
Jeder Kandidat darf nun höchstens einmal verwendet werden, und candidates kann wiederholte Werte enthalten. Wie änderst du die Suche, damit keine Kombination zweimal vorkommt?
Tipps
Öffne sie nacheinander. Jeder verrät ein bisschen mehr.
[2, 3, 3]und[3, 2, 3]sind dieselbe Kombination. Wenn du eine Kombination immer nur mit aufsteigend angeordneten Werten bildest, auf wie viele Arten kann jede Kombination gebildet werden?Sortiere die Kandidaten und erweitere eine Kombination Wert für Wert. Nachdem du
nums[i]hinzugefügt hast, darf der nächste Wert erneutnums[i]oder ein beliebiger späterer Wert sein, niemals ein früherer.Schreibe
backtrack(start, remaining). Wennremaining0 ist, speichere eine Kopie der aktuellen Werte. Andernfalls durchlaufe die Werte abstart: Füge einen Wert hinzu, rufe die Funktion mit demselben Index und dem kleineren Rest rekursiv auf und entferne dann den Wert. Beende die Schleife beim ersten Wert, der größer alsremainingist.
Lösung
Jede Antwort ist eine Multimenge von Kandidaten, und die Falle besteht darin, dieselbe Multimenge mehr als einmal zu erstellen: Die Auswahl von 2, dann 3, dann 3 und die Auswahl von 3, dann 2, dann 3 führen zur selben Kombination. Die Lösung besteht darin, jede Kombination in aufsteigender Reihenfolge zu erstellen, sodass es genau eine Möglichkeit gibt, sie zu erstellen, und die Kandidaten zu sortieren, damit ein Zweig sofort endet, sobald der nächste Wert größer ist als der verbleibende Wert. Derselbe aufsteigende Durchlauf liefert dir die Kombinationen in lexikografischer Reihenfolge, ohne dass eine abschließende Sortierung nötig ist.
Probiere jede Anzahl jedes Kandidaten aus
Korrekt, wird aber bei den größten Tests nicht fertig
Idee
Eine Kombination wird vollständig dadurch beschrieben, wie viele Exemplare jedes Kandidaten sie verwendet. Für [6, 2, 3] und das Ziel 8 besteht die Antwort [2, 3, 3] aus einer 2, zwei 3ern und keiner 6. Eine Möglichkeit, alle Antworten zu finden, besteht also darin, jede mögliche Anzahl für jeden Kandidaten auszuprobieren und die Auswahlmöglichkeiten zu behalten, deren Summe genau target ergibt. Ein Kandidat c passt höchstens target / c Mal, seine Anzahl reicht also von 0 bis zu dieser Grenze.
Stell dir einen Entscheidungsbaum mit einer Ebene pro Kandidat vor, nachdem sie sortiert wurden. Auf Ebene i entscheidest du, wie viele Exemplare des i-ten Werts du nimmst, und jedes Blatt ganz unten steht für eine vollständige Auswahl von Anzahlen. Jedes Multiset hat genau eine Liste von Anzahlen, sodass keine Kombination zweimal gefunden wird. Wenn du zuerst die größte Anzahl ausprobierst, ergibt sich auch die erforderliche Reihenfolge: Wenn sich zwei Antworten erstmals bei der Anzahl eines bestimmten Werts unterscheiden, enthält diejenige mit mehr Exemplaren diesen kleinen Wert noch, während die andere bereits einen größeren enthält, und kommt daher zuerst.
Das Problem ist die Größe des Baums. Die Anzahl der Blätter ist das Produkt von target / c + 1 über alle Kandidaten: Für das sortierte [2, 3, 6] und das Ziel 8 sind das 5 × 3 × 2 = 30 Blätter für 3 Antworten. Jeder Kandidat, der größer als target / 2 ist, verdoppelt die Anzahl der Blätter, obwohl er höchstens einmal passt. 40 solcher Kandidaten allein ergeben also 2^40, also etwa 10^12, Blätter. Die großen Tests sind so aufgebaut, und dieser Ansatz kann sie nicht rechtzeitig abschließen.
Algorithmus
- Sortiere die Kandidaten und erstelle ein Array mit Zählwerten, einen pro Wert.
- Schreibe
choose(i, total), das den Zählwert des Werts am Indexifestlegt. - Setze für
kvontarget / nums[i]abwärts bis 0 den Zählwert aufkund rufechoose(i + 1, total + k × nums[i])auf. - Wenn jeder Wert einen Zählwert hat, behalte die Kombination, falls
totalgleichtargetist, und schreibe jeden Wert so oft auf, wie sein Zählwert angibt. - Rufe
choose(0, 0)auf. Die behaltenen Kombinationen sind bereits in lexikografischer Reihenfolge.
def combinationSum(candidates, target):
nums = sorted(candidates)
counts = [0] * len(nums)
result = []
def choose(i, total):
if i == len(nums):
if total == target:
combo = []
for value, k in zip(nums, counts):
combo.extend([value] * k)
result.append(combo)
return
# Most copies first, so the combinations come out in lexicographic order.
for k in range(target // nums[i], -1, -1):
counts[i] = k
choose(i + 1, total + k * nums[i])
counts[i] = 0
choose(0, 0)
return resultIn aufsteigender Reihenfolge zurückgehen und beschneiden
Idee
Baue jede Kombination Wert für Wert auf, so, wie du sie aufschreiben würdest: in aufsteigender Reihenfolge. Der Startindex erzwingt diese Reihenfolge. Nachdem du nums[i] platziert hast, darf der nächste Wert wieder nums[i] sein, weil ein Kandidat mehrfach vorkommen kann, oder ein beliebiger späterer Wert, aber niemals ein früherer. Daher durchläuft der Aufruf, der den Index i platziert hat, nur die Indizes ab i. Jede Kombination hat genau eine aufsteigende Reihenfolge und somit genau einen Pfad im Baum. Ein Duplikat wie [3, 2, 3] wird also nie erstellt.
Hier ist der gesamte Baum für die sortierte Folge [2, 3, 6] und das Ziel 8. An der Wurzel sind noch 8 übrig; sie probiert 2, 3 und 6 aus. Unter 2 sind noch 6 übrig. Unter 2, 2 sind noch 4 übrig, und bei 2, 2, 2 bleiben noch 2, die durch eine weitere 2 zur Lösung [2, 2, 2, 2] werden; bei 2, 2, 3 bleibt noch 1 übrig, und der Pfad endet erfolglos. Unter 2, 3 sind noch 3 übrig, und es dürfen nur 3 und 6 ausprobiert werden; die 3 ergibt [2, 3, 3]. Unter 2, 6 bleibt nichts übrig: [2, 6]. Unter 3 dürfen nur 3 und 6 ausprobiert werden, und bei 3, 3 bleiben noch 2 übrig, die keine der beiden Zahlen auffüllt. Unter 6 sind noch 2 übrig, und es darf nur 6 ausprobiert werden. Insgesamt zwölf Aufrufe, verglichen mit den 30 Blättern des ersten Ansatzes.
Durch Sortieren wird aus einer Sackgasse ein vorzeitiger Abbruch. Wenn nums[i] größer ist als der verbleibende Rest, ist auch jeder spätere Wert größer. Deshalb verlässt du die Schleife mit break, statt den Rest zu testen. Im obigen Baum betrachtet der Knoten 2, 2, 3 mit einem Rest von 1 die 3, stellt fest, dass sie nicht passt, und betrachtet die 6 gar nicht mehr. Die Suche besucht nur Präfixe, deren Summe höchstens target ist. Deshalb benötigen die großen Tests, die den ersten Ansatz ausbremsen, hier nur einige tausend Aufrufe.
Die Ausgabereihenfolge ergibt sich aus demselben Durchlauf. Auf jeder Ebene probiert die Schleife zuerst kleinere Werte aus, und jede Kombination wird in aufsteigender Reihenfolge geschrieben. Zwei Antworten unterscheiden sich erstmals auf der Ebene, auf der sich ihre Pfade verzweigen. Da der Pfad mit dem kleineren Wert dort zuerst erkundet wird, erscheinen die Antworten in lexikografischer Reihenfolge. Eine Kombination kann niemals das Präfix einer anderen sein, weil die Werte positiv sind und beide dieselbe Summe erreichen.
Algorithmus
- Sortiere die Kandidaten in aufsteigender Reihenfolge.
- Schreibe
backtrack(start, remaining), das eine Listepathgemeinsam verwendet. Wennremaining0 ist, speichere eine Kopie vonpath. - Andernfalls durchlaufe
ivonstartbis zum Ende. Wennnums[i] > remaining, brich ab: Jeder spätere Wert ist größer. - Füge
nums[i]hinzu, rufebacktrack(i, remaining-nums[i])mitiund nicht miti + 1auf, damit sich der Wert wiederholen kann, und entferne ihn anschließend. - Rufe
backtrack(0, target)auf und gib die gespeicherten Kombinationen zurück, die bereits in lexikografischer Reihenfolge vorliegen.
def combinationSum(candidates, target):
nums = sorted(candidates)
result = []
path = []
def backtrack(start, remaining):
if remaining == 0:
result.append(path[:])
return
for i in range(start, len(nums)):
if nums[i] > remaining:
break # sorted, so every later value is too big as well
path.append(nums[i])
backtrack(i, remaining - nums[i]) # i, not i + 1: nums[i] may repeat
path.pop()
backtrack(0, target)
return result
Stolperfallen und Grenzfälle
Die meisten falschen Antworten entstehen durch die Reihenfolge der Suche, nicht durch die Rechnung.
- Auf jeder Ebene jede mögliche Zahl durchzugehen, statt beim aktuellen Index zu beginnen, erzeugt
[2, 3, 3],[3, 2, 3]und[3, 3, 2]als drei Antworten. Jede Antwort zu sortieren und anschließend Duplikate zu entfernen, ergibt zwar die richtige Liste, verursacht aber exponentiell mehr Arbeit. - Mit
i + 1statt mitirekursiv weiterzugehen, sorgt dafür, dass jeder Wert nur einmal vorkommen kann. Dadurch fehlt[2, 2, 2, 2]. pathselbst statt einer Kopie zu speichern: Dann ist jede gespeicherte Antwort dieselbe Liste, die durch das Backtracking am Ende geleert wurde.breakbei Kandidaten zu verwenden, die du nicht sortiert hast. Bei[6, 2, 3]und einer verbleibenden Summe von 2 bricht die Schleife bei 6 ab und versucht die 2 nie.- Die Kombinationen in der Reihenfolge zurückzugeben, die die unsortierte Eingabe vorgibt. Die erwartete Liste ist lexikografisch sortiert; die sortierte Suche liefert sie ohne zusätzliche Sortierung.
- In Lua und R beginnen Arrays bei 1. Daher startet der erste Aufruf bei Index 1, und die Schleife läuft bis zur Länge des Arrays.
Häufige Fragen4
Wie hoch ist die Zeitkomplexität von Combination Sum?
Die Backtracking-Suche ist exponentiell. Bei n Kandidaten, dem Zielwert t und dem kleinsten Kandidaten m enthält eine Kombination höchstens t/m Werte, und jeder Schritt hat höchstens n Auswahlmöglichkeiten. Dadurch ist der Aufwand nach oben durch O(n^(t/m)) beschränkt. Das Beschneiden bei sortierten Kandidaten hält die tatsächliche Anzahl der Aufrufe weit darunter, da die Suche nur Präfixe besucht, deren Summe noch höchstens t beträgt. Der zusätzliche Speicherbedarf beträgt O(t/m) für den aktuellen Pfad und den Aufrufstapel, zuzüglich der Ausgabe.
Warum rufst du bei Combination Sum die Rekursion mit i und nicht mit i + 1 auf?
Die Rekursion mit i ermöglicht es, dass der nächste Wert erneut derselbe Kandidat ist. So kann ein Wert mehrmals verwendet werden. Die Rekursion mit i + 1 geht über ihn hinaus und macht das Problem zu der Variante, bei der jeder Kandidat höchstens einmal verwendet wird. Die andere Hälfte der Regel ist ebenso wichtig: Wenn man nie zu einem Index vor i zurückgeht, bleibt jede Kombination aufsteigend sortiert, und Duplikate werden verhindert.
Wie vermeidest du doppelte Kombinationen ohne eine Menge?
Erzeuge jede Kombination in einer festen, aufsteigenden Reihenfolge. Der Startindex erzwingt dies: Nachdem nums[i] hinzugefügt wurde, sucht die Suche nur nach nums[i] und späteren Werten. Jede Kombination hat dann genau einen Pfad im Suchbaum, wird also einmal erzeugt, und weder eine Menge noch eine abschließende Duplikatentfernung ist erforderlich.
Kann Combination Sum mit dynamischer Programmierung gelöst werden?
Ja. Bewahre für jede Summe von 0 bis zum Ziel die Liste der Kombinationen auf, die sie ergeben, und füge jeweils einen Kandidaten hinzu, sodass die Werte in jeder Liste aufsteigend bleiben – nach demselben Prinzip wie beim Zählen der Möglichkeiten, Wechselgeld herauszugeben. Dabei wird keine Sackgasse zweimal erkundet, aber es werden alle partiellen Kombinationen für jede Summe gespeichert. Das benötigt deutlich mehr Speicher als Backtracking, und die endgültige Liste muss möglicherweise sortiert werden. Da die Ausgabe selbst exponentiell groß sein kann, ist Backtracking üblicherweise die richtige Wahl.
Ähnliche Aufgaben
Aufgaben mit denselben Ideen. Wer zwei oder drei davon löst, behält das Muster.
Python
def combinationSum(candidates, target):
# Schreibe hier den CodeFall 1
Fall 2
Fall 3
Eingabe
candidates = [6, 2, 3] target = 8
Erwartet
[[2, 2, 2, 2], [2, 3, 3], [2, 6]]