Combination Sum
Combination Sum is LeetCode 39 (Medium). You are given an array of distinct positive integers candidates and a positive target. Return every unique combination of candidates whose numbers add up to target, in any order.
- A candidate may be used any number of times.
- Two combinations are the same if they use the same numbers the same number of times, in any order.
- The inputs are chosen so there are fewer than 150 combinations.
- 1 <= candidates.length <= 30
- 2 <= candidates[i] <= 40
- All elements of candidates are distinct.
- 1 <= target <= 40
Intuition
Think of the answer as a search tree. The root is the target, each edge subtracts one candidate, and each node records how much is still needed. A node at 0 is a solution; a candidate larger than what remains is a dead end.
Two rules keep the combination sum solution honest. Duplicates like [2, 3] and [3, 2] disappear if a branch only picks candidates at or after the last one chosen, because every combination then has one sorted spelling. Reuse is allowed by letting a branch pick the same candidate again. With sorted input, the first candidate that is too big proves every later one is too, so whole subtrees are skipped.
The question asks for all combinations, not a count or a best value. Listing every solution means exploring choices and undoing them: backtracking. If it only asked for the number of combinations, a DP like Coin Change II would be enough.
Approach
Before reading on, draw the tree for [2, 3, 6, 7] and target 7 by hand. How do you stop [2, 2, 3] from also appearing as [2, 3, 2]? Where can you stop searching early?
Sort the candidates
Sort the candidates first, at O(n log n). Sorting is what makes pruning possible: once candidates[i] > remain, every later candidate is larger too, so the loop can stop instead of testing values that cannot fit.
Define the recursive call
Write dfs(start, remain), which tries every candidate from index start onward with remain still to reach, and keep one shared path list for the current choices. Passing start down is what stops a branch from reaching back to earlier candidates and building the same combination in another order.
Handle the two ends of a branch
remain == 0: record a copy ofpathand return. The list itself keeps changing as the search backtracks, so storing it directly leaves every saved answer empty.candidates[i] > remain: break; with sorted input nothing from here on can fit.
Choose, explore, un-choose
Append c, call dfs(i, remain - c), then pop c. Recursing with the same i rather than i + 1 is what lets a number be used again. Popping restores path, so the next candidate in the loop starts from exactly the state this call received.
Combination Sum solution in Python | C++ | Java
[2,3] and [3,2] from both being produced.[2,3] and [3,2] from both being produced.[2,3] and [3,2] from both being produced.Common pitfalls
Restarting the loop at index 0
for i in range(len(candidates)):
...
dfs(i, remain - c)for i in range(start, len(candidates)):
...
dfs(i, remain - c)Starting from 0 lets a call go back to smaller candidates, so [2, 2, 3], [2, 3, 2] and [3, 2, 2] are all produced. Starting at start builds each combination in non-decreasing order, exactly once.
Recursing with i + 1
dfs(i + 1, remain - c)
dfs(i, remain - c)
i + 1 forbids reusing a number, which is Combination Sum II's rule. For [2, 3, 6, 7] and 7 it would miss [2, 2, 3].
Saving the path without copying it
res.append(path)
res.append(path[:])
path is one shared list that keeps changing. Appending it stores a reference, so after the search every saved answer is the same empty list.
Complexity
Combination Sum I, II, III and IV
Four problems with nearly the same name. The rules on reuse, duplicates and order decide the technique.
| Problem | Reuse a number? | Input | Output | Technique |
|---|---|---|---|---|
| Combination Sum (39) | yes | distinct numbers | all combinations | backtracking, recurse with i |
| Combination Sum II (40) | no, each once | may contain duplicates | all unique combinations | backtracking with i + 1, skip equal neighbours |
| Combination Sum III (216) | no | digits 1 to 9, exactly k of them | all combinations | backtracking with a size limit |
| Combination Sum IV (377) | yes | distinct numbers | count of ordered sequences | DP, amount in the outer loop |
Combination Sum FAQ
What is the combination sum problem?
Given distinct positive numbers and a target, list every combination of those numbers (each usable any number of times) that adds up to the target. For [2, 3, 6, 7] and 7 the answer is [[2, 2, 3], [7]].
How does the combination sum backtracking algorithm work?
- Method: backtracking over a search tree whose nodes are the remaining target.
- Avoid duplicates: each call only uses candidates from its start index onward.
- Allow reuse: recurse with the same index
i. - Base cases: remaining 0, save a copy of the path; candidate larger than remaining, stop the loop (the array is sorted).
- Steps: choose, recurse, un-choose.
- Example:
[2, 3, 6, 7], 7 gives[2, 2, 3]and[7].
Why sort the candidates in combination sum?
After sorting, once one candidate is larger than the remaining amount, every later candidate is too. The loop can break instead of continue, which removes whole subtrees from the search.
How is Combination Sum different from Combination Sum II?
In Combination Sum (39) each number can be used many times and the input has no duplicates, so you recurse with i. In Combination Sum II (40) each number is used at most once and the input can contain duplicates, so you recurse with i + 1 and skip equal numbers at the same level.
Can combination sum be solved with dynamic programming?
Counting the combinations can (that is Coin Change II). Listing them all still needs every combination to be built, so backtracking is the natural fit; a DP that stores lists would use a lot of memory for no gain.