Combination Sum
Reuse allowed: recurse on the same index until the remaining target goes negative.
Approach
Backtrack, subtracting a chosen candidate from the target. Since numbers can repeat, recurse with the same start index; move to the next index only when you decide to stop using the current candidate. Prune when the remainder drops below zero.
Time complexity
Exponential (bounded by target/candidates)
Space complexity
O(target) recursion
Common mistake
Advancing the start index after every pick, which forbids reusing a number the problem allows.
Interactive walkthrough — step through it below, free and without an account
Problem
Given a list of distinct positive integers and a target value, return every unique combination whose values add up exactly to the target. A candidate may be selected more than once, and combinations may be returned in any order. Do not include duplicate combinations that differ only in ordering.
Examples
Input: candidates = [2,3,6,7], target = 7
Output: [[2,2,3],[7]]
Input: candidates = [2,4,5], target = 8
Output: [[2,2,2,2],[2,2,4],[4,4]]
Problem
Problem
Given a list of distinct positive integers and a target value, return every unique combination whose values add up exactly to the target. A candidate may be selected more than once, and combinations may be returned in any order. Do not include duplicate combinations that differ only in ordering.
Problem and goal
Learning goal: Recognize what must be enumerated and why repeated choices are legal.
Core idea
We are not looking for one pair or one path; we must list every combination whose sum equals target. The order of values inside a combination does not matter, so [2,3,2] should not become a second answer alongside [2,2,3]. The crucial twist is that a candidate can be selected again.
Algorithm plan
- Start with an empty
pathand the fulltargetas the remainder. - Choose a candidate at or after
start. - Subtract it from the remainder and recurse.
- Record the path when the remainder is exactly zero.
Micro-example
For [2,3,6,7] and target 7, choosing 2 leaves 5, and choosing 2 again leaves 3. Choosing 3 next reaches zero, producing [2,2,3].
Pitfalls
- Treating the problem like permutations creates duplicate orderings.
- Advancing past the chosen index immediately forbids reuse.
- A path with a negative remainder can never recover because all values are positive.
Recap
- Build combinations incrementally.
- Reuse is allowed.
- Use index order to avoid duplicate orderings.
Problem and goal
The search-space contract
A valid branch may choose the same candidate repeatedly, but it should never move backward through the candidate list.
Reuse the current index; do not restart at index 0 or advance automatically.
Key idea: same-index recursion
Learning goal: Understand why reuse requires recursing with the same candidate index.
Core idea
At a recursive call, start means "the earliest candidate that may be used next." If we choose candidates[i], we recurse with i, not i + 1, because the same value remains available. Moving to i + 1 is a separate decision: it means we stop using the current candidate and consider later values.
Algorithm plan
- At index
i, branch by choosingcandidates[i]. - Keep
ias the nextstartwhile the candidate may be reused. - Eventually skip to
i + 1through the loop's later iterations. - Stop a branch once its remainder is negative.
Key insight
The recursive call with the same index represents "use this candidate again." The loop moving forward represents "try a different candidate."
Micro-example
After choosing 2 from [2,3,6,7], the state is path = [2], remaining = 5, start = 0. Choosing index 0 again creates [2,2]; choosing index 1 instead creates [2,3].
Pitfalls
- Passing
i + 1after every pick silently changes the problem. - Restarting from
0allows descending orders and duplicate combinations. - Forgetting that
startcontrols ordering, not whether a value exists globally.
Recap
- Pick: recurse with
i. - Skip current candidate: let the loop reach
i + 1. - This separates reuse from duplicate avoidance.
Key idea: same-index recursion
Choose candidate 2 again and recurse with start = 0.
[2] -> [2,2]
Main approach: backtracking
Learning goal: Translate the search idea into a correct recursive procedure.
Core idea
Backtracking explores one candidate choice, recursively completes that partial combination, and then removes the choice before trying the next option. Because every candidate is positive, the remainder only decreases. That gives a safe and simple pruning rule: once remaining < 0, no extension of the current path can work.
Algorithm plan
search(start, remaining, path):
if remaining == 0:
save a copy of path
return
if remaining < 0:
return
for i from start through the last candidate:
path.append(candidates[i])
search(i, remaining - candidates[i], path)
path.pop()The call search(i, ...) is the defining detail. It allows the selected value to be used again, while the loop's increasing indices ensure that combinations are generated in one canonical order.
Micro-example
For path = [2,2] and remaining = 3, choosing index 1 adds 3, records [2,2,3], then removes 3 to restore [2,2].
Pitfalls
- Save a copy of
path, not the mutable list itself. - Backtrack after every recursive call.
- Check
remaining == 0before exploring more candidates.
Recap
- Choose, recurse, undo.
- Recurse with the chosen index.
- Prune negative remainders immediately.
Main approach: backtracking
After choosing candidates[0] = 2, which recursive start index allows another 2?
Walkthrough: [2,3,6,7], target 7
Learning goal: Follow the complete search tree and see both successful and pruned branches.
Core idea
The path is the current combination, while remaining is the amount still needed. At each level, the loop considers candidates from start onward. The search first explores smaller values, so it discovers [2,2,3]; later it explores the direct choice [7].
Algorithm plan
- Begin with
path = [],remaining = 7, andstart = 0. - Choose
2repeatedly by keepingstart = 0. - Add
3when the remainder becomes3; record the completed path. - Backtrack, try other candidates, and prune any negative remainder.
- Eventually choose
7from the root and record the second answer.
Micro-example
The successful chain is 7 -> 5 -> 3 -> 0, corresponding to picks 2, 2, 3. Another root branch picks 7 and immediately reaches 0.
Why it works
Every valid combination has one nondecreasing representation, and the loop eventually tries each allowed next candidate at that representation's first unused position.
Pitfalls
- A failed branch is not an error; it is useful evidence for pruning.
- Removing the last path value must happen when returning from every branch.
- The direct
[7]answer is easy to miss if the loop stops after smaller candidates.
Recap
- The same index enables
[2,2,3]. - Negative remainder branches are discarded.
- The root also tries
7, yielding[7].
Walkthrough: [2,3,6,7], target 7
Trace every important choice, undo, prune, and solution for candidates [2,3,6,7] and target 7.
Pitfalls and edge cases
Learning goal: Identify the implementation mistake that most often breaks this solution.
Core idea
The most dangerous bug is advancing the start index after every pick. Writing search(i + 1, ...) treats each candidate as usable at most once, which solves a different problem. Combination Sum explicitly permits repetition, so the recursive call must use i.
Data structure model
path: the current combination under construction.start: the first candidate index allowed at this depth.remaining: target still needed.answers: completed path copies.
Micro-example
For target 7, the valid answer [2,2,3] requires choosing index 0 twice. If the first 2 changes start to 1, the second 2 is unavailable and the answer disappears.
Common pitfall
Advancing start after every pick forbids reuse. Recurse with i when choosing a candidate; only later loop iterations move to larger indices.
Pitfalls
- Forgetting to pop after recursion leaves stale values in later paths.
- Recording
pathby reference can make every answer change later. - Returning only when
remaining < 0misses completed combinations at zero. - Assuming candidates are sorted when using a sort-dependent optimization without sorting first.
Recap
search(i, ...)means reuse is allowed.search(i + 1, ...)means skip the current candidate.- Zero records; negative prunes; backtracking restores state.
Pitfalls and edge cases
What is wrong with calling search(i + 1, ...) immediately after choosing candidates[i]?
Complexity and wrap-up
Learning goal: State the cost of exhaustive combination search and remember the reusable template.
Core idea
Backtracking may explore exponentially many paths because the number of valid and invalid combinations grows rapidly with the target and the candidate values. The positive-number constraint bounds the depth: a path can contain at most target / min(candidates) values. The recursion stack therefore uses space proportional to the maximum path length.
Algorithm plan
- Let
mbe the number of candidates. - Explore each candidate choice from the current
startindex. - Recurse with the same index to model unlimited reuse.
- Stop at zero or prune below zero.
- Include the output list in any total-space discussion because answers themselves may be large.
Micro-example
With target 7 and smallest candidate 2, no path can contain more than 3 values before exceeding the target. Larger targets or smaller candidates create deeper and broader search trees.
Pitfalls
- Calling the runtime polynomial ignores the output-sensitive search tree.
- Saying space is only
O(1)overlooks the recursion path. - Forgetting that returned combinations also consume memory.
Recap
- Time is exponential in the bounded target/candidate search space.
- Auxiliary recursion space is
O(target)under the usual positive-integer bound. - The reusable template is choose, recurse with
i, undo, and prune.
Complexity and wrap-up
Summarize the exponential search and recursion depth.
Now try it on your own problem
Paste any LeetCode problem — or one of your own — and codebrew builds a walkthrough like this one for it.
Build a lesson from any problem
