Algorithm

Combination Sum

Backtracking Pattern

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.

CONSTRAINTS
  • 1 <= candidates.length <= 30
  • 2 <= candidates[i] <= 40
  • 1 <= target <= 500
  • All candidates are distinct positive integers
  • Each candidate may be reused without limit
EXAMPLE 1
Input: candidates = [2,3,6,7], target = 7
Output: [[2,2,3],[7]]
2 + 2 + 3 = 7 reuses the candidate 2 twice, which is permitted. 7 alone also works. Note [3,2,2] does not appear separately — it is the same multiset as [2,2,3], and only one representative belongs in the output.
EXAMPLE 2
Input: candidates = [2,3,5], target = 8
Output: [[2,2,2,2],[2,3,3],[3,5]]
A single candidate may be repeated as many times as needed, so four 2s is a valid answer. Each combination is listed in non-decreasing order, which is how duplicates are avoided rather than filtered.
EXAMPLE 3
Input: candidates = [2], target = 1
Output: []
The smallest candidate already exceeds the target, so no sum can reach it. Every candidate is at least 2, so odd targets below the minimum are unreachable and an empty list is the correct answer.
EXAMPLE 4
Input: candidates = [7,3,2], target = 7
Output: [[7],[3,2,2]]
Same numbers as the first example minus 6, but supplied unsorted. The combinations are built in the order candidates appear, so [3,2,2] is non-decreasing with respect to the input's positions, not to the values — sorting first would give [2,2,3] instead. Either is accepted.
How many times may a single candidate be reused?
Without limit, bounded only by the target. That is the defining difference from Combinations, and in code it is a one-character change — passing i instead of i+1 to the recursive call.
Can the candidates contain duplicates or non-positive numbers?
Not here — all are distinct and at least 2. Both guarantees matter. Duplicate candidates would require an extra skip rule; a zero or negative candidate would make the search non-terminating, since the running total would stop decreasing.
Do [2,2,3] and [2,3,2] count as different answers?
No, they are the same combination and exactly one may appear. Suppressing the reorderings is what the start index accomplishes.
Would sorting the candidates first help?
It is not required for correctness, but it enables a stronger pruning rule: once a candidate exceeds the remaining amount, every later candidate does too, so the loop can break rather than continue. Worth mentioning even if you do not implement it.

Two things separate this from Combinations, and taking them one at a time makes the solution fall out.

First, candidates may be reused. Second, the answer is not defined by a length but by a running total, which introduces a genuine new capability: the search can now tell it has failed before reaching any leaf.

Reuse changes exactly one character

Recall how Combinations avoided duplicates: after choosing the number at index i, the next choice came from index i+1 onward. Paths were strictly increasing, so each set had exactly one representation and reorderings could not be built.

Here the same duplicate problem exists — [2,2,3] and [2,3,2] and [3,2,2] describe one answer — so the same idea applies, with one relaxation. Paths must be non-decreasing rather than strictly increasing, because repeating a value is now legal while going backwards still is not.

In code that is the difference between passing i + 1 and passing i:

- build(i + 1) — each candidate used at most once. That is Combinations.
- build(i) — the current candidate remains available, but earlier ones do not. That is this problem.

Passing i keeps index i in the choice space while permanently excluding everything before it, which is precisely "may repeat, may not go back". One character encodes the entire difference between two problems, and being able to say why is worth more than memorising either.

The running total enables real pruning

Now the more interesting change. Track how much of the target remains, subtracting each chosen candidate.

Three situations arise at any node, and only one continues:

- Remainder is zero. The path sums exactly to the target. Record it and stop — every candidate is positive, so adding anything more can only overshoot.
- Remainder is negative. The path has overshot. Stop, and stop permanently: with all candidates positive the total only grows, so no continuation can recover.
- Remainder is positive. Keep going.

That second case is the new capability. In Subsets, Combinations and Permutations, every partial path could still become a valid answer, so the search never abandoned anything — it simply ran to the leaves. Here a branch can be proven hopeless partway down and cut immediately, along with everything beneath it.

Key Insightpruning requires a property that, once violated, can never be repaired. "The total exceeds the target" qualifies because all candidates are positive — that guarantee is what makes overshooting permanent. Allow a single negative candidate and the pruning becomes invalid, since a later subtraction could bring the total back down.
The mechanism
python
def combination_sum(candidates, target):
    result = []
    path = []

    def build(start, remain):
        if remain == 0:
            result.append(list(path))     # exact hit: snapshot a copy
            return
        if remain < 0:
            return                        # PRUNE: overshot, unrecoverable

        for i in range(start, len(candidates)):
            path.append(candidates[i])            # CHOOSE
            build(i, remain - candidates[i])      # EXPLORE — 'i', not 'i + 1'
            path.pop()                            # UN-CHOOSE

    build(0, target)
    return result
Crucial Notebuild(i, ...) passes i, keeping the current candidate available for reuse. Writing i + 1 silently converts this into a different problem — it still runs, still returns combinations, and quietly omits every answer containing a repeat.
Crucial Notethe remain < 0 check must come before the loop, not inside it. Placing it inside would still work but wastes a function call per doomed branch. More importantly, both base cases must be tested before any choosing happens, or a path that has already hit zero would continue extending itself.
Crucial Notethis search terminates only because every candidate is at least 1. A candidate of 0 would leave the remainder unchanged forever, and the recursion would run until the stack overflows. Confirming that candidates are positive is a real correctness check, not a formality.
Sharpening the pruning

The version above discovers an overshoot only after recursing into it — the child call computes a negative remainder and returns immediately. That call was avoidable.

Sort the candidates first. Then, once a candidate exceeds the remainder, every subsequent candidate does too, so the loop can break rather than skip:

python
def combination_sum(candidates, target):
    candidates.sort()                     # enables the break below
    result, path = [], []

    def build(start, remain):
        if remain == 0:
            result.append(list(path))
            return

        for i in range(start, len(candidates)):
            if candidates[i] > remain:
                break                     # sorted: every later candidate is worse too
            path.append(candidates[i])
            build(i, remain - candidates[i])
            path.pop()

    build(0, target)
    return result

The remain < 0 base case is now unreachable and can be dropped, since no overshooting call is ever made. The complexity bound is unchanged — it is dominated by the answers actually produced — but the constant improves substantially, and on adversarial inputs it is the difference between passing and timing out.

Worked example:candidates = [2,3,6,7], target = 7

Sorted already. Following the branch starting with 2:

- build(0, 7), path []. Choose 2, remainder 7-2 = 5.
- build(0, 5) — start is still 0, so 2 is available again. Choose 2, remainder 3.
- build(0, 3). Choose 2, remainder 1.
- build(0, 1). Candidate 2 exceeds the remainder 1, so break immediately — candidates 3, 6 and 7 are never even examined. Return with nothing.
- Pop. Choose 3, remainder 0. → record [2,2,3]. Pop.
- Choose 6: exceeds 3, break.
- Pop. Choose 3, remainder 5-3 = 2.
- build(1, 2) — start is 1, so 2 is out of reach. The smallest available candidate is 3, which exceeds 2, so break. Nothing found.
- This is the start index working: [2,3,2] cannot be constructed, so the duplicate never arises.
- Pop. Choose 6: exceeds 5, break.
- Pop, back to []. Choose 3, remainder 4. Its subtree finds nothing — 3+3 = 6 overshoots at remainder 1, and 6 and 7 are too large.
- Choose 6, remainder 1: everything available exceeds it. Nothing.
- Choose 7, remainder 0 → record [7].

Result [[2,2,3],[7]]. The two moments worth noticing are the break at remainder 1, which skipped three candidates without testing them, and the start index at build(1, 2), which made the duplicate [2,3,2] structurally impossible.

Where this sits in the family

The section's skeleton is unchanged; only the slots differ:

- Subsets — take or skip each element; every path is an answer.
- Combinations — choose from index i+1 onward; answers have length k.
- Combination Sum — choose from index i onward; answers hit a target sum.
- Permutations — choose any unused element; answers use everything.

Two independent decisions determine which you are in. Does order matter? If not, use a start index. May items repeat? If so, pass i; if not, i + 1.

The genuinely new idea here is pruning on the objective. Earlier problems pruned structurally, if at all — the start index prevented duplicates from being built. This one prunes on a value computed from the path, abandoning branches that have already failed. N-Queens and Sudoku take that much further, testing constraints at every single placement.

Recognition signals: make change with given coins, reach a target sum, fill a knapsack exactly, all ways to score n points. A caveat worth stating: if the question asks only how many ways, or only the minimum number of items, dynamic programming is far better — backtracking is for when you must produce the combinations themselves.

Train the reflex: when the path carries a running value with a monotone relationship to the goal, check it at every node. A branch you can prove hopeless is a whole subtree you never have to walk.

Interactive Strategy Visualization

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.

Available Choices
2
3
5
Target To Reach
5
Your Current Path
[ Empty ]
Start with an empty path and a target of 5.
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
1. Choose: Add number to path.
2. Explore: Call recursion.
3. Un-choose: Remove it to try others.
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.

Exponential Enumerate Then Filter By Sum
O(N^(T/M)) Backtracking With Overshoot Pruning
O(N^(T/M)) Sorted With Early Loop Break