Combination Sum
Given an array of distinct positive integers and a target, return every unique combination of the candidates that sums exactly to the target.
The same candidate may be used any number of times, so [2,2,3] is valid when 2 and 3 are both available. Two combinations are the same if they contain the same numbers with the same multiplicities regardless of order, so [2,2,3] and [2,3,2] count as one answer and only one may appear.
Return an empty list when no combination reaches the target exactly.
- 1 <= candidates.length <= 30
- 2 <= candidates[i] <= 40
- 1 <= target <= 500
- All candidates are distinct positive integers
- Each candidate may be reused without limit
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[]candidates = [7,3,2], target = 7[[7],[3,2,2]]Pick numbers that add up to target, and you may reuse a number freely. Same "no duplicate sets" issue as Combinations, so keep a start index — but allow the same number again by recursing with i (not i + 1). New power: track the amount remaining, and stop a branch the moment it goes negative.
Every problem in this section is the same loop — three beats: choose, explore, un-choose.
def backtrack(state):
if done(state):
save(state) # record the finished answer
return
for choice in choices(state): # what can I pick right now?
if not ok(choice):
continue # prune: skip bad picks early
apply(choice) # CHOOSE
backtrack(next_state) # EXPLORE
undo(choice) # UN-CHOOSE: put it backOnly four slots change between problems: done, choices, ok (pruning), and how you finish (save every answer, or return True at the first one). Fill those and the problem is solved.
i from start..end (reuse allowed → recurse with i).start onward.0 → save the path.< 0 → stop (safe because all numbers are positive).def combination_sum(candidates, target):
res, path = [], []
def backtrack(start, remain):
if remain == 0:
res.append(list(path))
return
if remain < 0:
return # PRUNE: overshot
for i in range(start, len(candidates)):
path.append(candidates[i]) # CHOOSE
backtrack(i, remain - candidates[i])# EXPLORE: 'i' allows reuse
path.pop() # UN-CHOOSE
backtrack(0, target)
return resbacktrack(i, ...) is the whole difference from Combinations — i keeps the current number available, i + 1 would forbid reuse. Overshoot pruning is valid only because every candidate is positive, so once you pass the target you can never come back.
Trace [2,3,6,7], target 7: 2+2+3 = 7 → [2,2,3]; 7 → [7]. Result [[2,2,3],[7]].
The Backtracking Mindset
Think of this as exploring a maze. When you hit a dead end (target < 0), you step back and try a different path.
The Choice Tree
Every time we pick a number, we create a new branch. We keep picking until we either hit the Target (Success) or Overshoot (Failure).
The 3-Step Pattern
Smart Pruning
If our sum is already too big, any further numbers will only make it bigger. We "Prune" (cut) the branch early to save time.