Example 1
ready- Input
candidates = [ 2, 3, 6, 7 ] target = 7- Expected
[ [ 2, 2, 3 ], [ 7 ] ]- Why
- 2 + 2 + 3 and the single 7 are the two unique combinations
A café has a short menu of voucher values, and you may use any voucher as many times as you like. The interesting part isn't finding one way to pay — it's finding every way.
Treat the distinct positive integers in candidates as the voucher values you can use.
Build every unique combination — a selection where order doesn't create a new answer
— whose values add up to target.
The output follows these rules:
[] when no combination reaches the target.1 <= candidates.length <= 82 <= candidates[i] <= 40candidates are distinct.1 <= target <= 40Hints
For a candidate value v, only 0 through target // v copies can fit. Trying
every count for each candidate gives you a correct baseline.
Sort a copy of candidates. During the search, once a candidate is larger than the
remaining sum, every later candidate is too large as well.
After choosing candidates[i], recurse from index i, not i + 1, so that value
may be chosen again. Never move backward; that keeps combinations non-decreasing and
prevents reordered duplicates.
What changes if each candidate may be used only once and the input itself can contain duplicate values?
Visible cases
candidates = [
2,
3,
6,
7
]
target = 7[
[
2,
2,
3
],
[
7
]
]candidates = [
2,
3,
5
]
target = 8[
[
2,
2,
2,
2
],
[
2,
3,
3
],
[
3,
5
]
]candidates = [
2
]
target = 1[]Interview signal
Unlimited reuse makes the search tree deep, but positive values give you a hard brake: the remaining sum always falls after a choice.
Let n be the number of candidates, m the smallest candidate, d = target // m the
maximum answer length, and k the number of returned combinations.
Multiplicity means the number of copies you take. For each candidate, try every legal multiplicity before moving to the next candidate. Because candidates are processed in sorted order, every completed path is already non-decreasing and represents one unique combination.
def combination_sum(candidates: list[int], target: int) -> list[list[int]]:
values = sorted(candidates)
answer: list[list[int]] = []
path: list[int] = []
def search(index: int, remaining: int) -> None:
if index == len(values):
if remaining == 0:
answer.append(path.copy())
return
value = values[index]
max_count = remaining // value
for count in range(max_count + 1):
path.extend([value] * count)
search(index + 1, remaining - count * value)
for _ in range(count):
path.pop()
search(0, target)
return answerThis baseline tests a full grid of candidate counts, including many partial assignments that can't finish the remaining sum.
Time O((d + 1)^n · d). Space O(n + d + k · d) including recursion, the current combination, and the returned output.
Backtracking chooses a candidate, explores that choice, then un-chooses it. The partial sums form a solution-space tree, a tree whose nodes are growing combinations. Sorting lets the search prune, or stop exploring, every later choice as soon as one value is too large. This is the choose/explore/un-choose backtracking pattern with reuse kept on the explore step.
def combination_sum(candidates: list[int], target: int) -> list[list[int]]:
values = sorted(candidates)
answer: list[list[int]] = []
path: list[int] = []
def search(start: int, remaining: int) -> None:
if remaining == 0:
answer.append(path.copy())
return
for i in range(start, len(values)):
value = values[i]
if value > remaining:
break
path.append(value) # choose
search(i, remaining - value) # explore; i allows reuse
path.pop() # un-choose, then the loop advances
search(0, target)
return answerPassing i keeps the chosen value available. Passing start forward and never backward
means [2, 2, 3] is generated, while reordered copies such as [3, 2, 2] never enter
the tree. Positive candidates guarantee the recursion reaches either zero or a dead end.
Time O(n^d · d). Space O(d + k · d) in a loose worst-case bound, including the returned output. In practice, sorted-order pruning removes most of that theoretical tree.
Ready to run.
3 cases are queued.
Visible testcase
Case 1
Expected
[
[
2,
2,
3
],
[
7
]
]