39. Combination Sum
Quick Identifier
- Topic: backtracking
- Pattern: DFS / Backtracking
- Difficulty: Medium
- Companies: Amazon, Google, Meta, Microsoft, Bloomberg
- Frequency: Very High
- LeetCode: 39
Quick Sights
- Time Complexity:
O(2^target)from the optimal approach. - Space Complexity:
O(recursion depth)excluding the final output unless stated. - Core Mechanism: Given distinct positive
candidatesandtarget, return all unique combinations that sum totarget. The same number may be reused unlimited times.
Concept Explanation
Given distinct positive candidates and target, return all unique combinations that sum to target. The same number may be reused unlimited times.
State the invariant before coding: what partial result is already correct, what work remains, and what value or state each recursive/iterative step must preserve.
Recognition cues:
- Sum exactly target with reuse
- Combinations not permutations — enforce non-decreasing index order in DFS
- Positive integers → bounded recursion
Study Pattern (SDE3+)
- 0-3 min: restate I/O and brittle edges aloud, then verbalize two approaches with time/space before you touch the keyboard.
- Implementation pass: one-line invariant above the tight loop; state what progress you make each iteration.
- 5 min extension: pick one constraint twist (immutable input, stream, bounded memory, parallel readers) and explain what in your solution breaks without a refactor.
Diagram
This diagram shows the algorithm movement for this problem family.
Loading diagram…
Approaches
- Brute — generate multisets — exponential waste.
- Optimal — DFS with start index, prune when sum > target —
O(2^target)worst conceptual; tight in practice with pruning.
Optimal Solution
Go
func combinationSum(candidates []int, target int) [][]int {
result := [][]int{}
path := make([]int, 0, 16)
var dfs func(start int, remaining int)
dfs = func(start int, remaining int) {
if remaining == 0 {
copied := make([]int, len(path))
copy(copied, path)
result = append(result, copied)
return
}
if remaining < 0 {
return
}
for index := start; index < len(candidates); index++ {
value := candidates[index]
path = append(path, value)
dfs(index, remaining-value)
path = path[:len(path)-1]
}
}
dfs(0, target)
return result
}JavaScript
function combinationSum(candidates, target) {
const result = [];
const path = [];
function dfs(startIndex, remaining) {
if (remaining === 0) {
result.push([...path]);
return;
}
if (remaining < 0) {
return;
}
for (let index = startIndex; index < candidates.length; index += 1) {
path.push(candidates[index]);
dfs(index, remaining - candidates[index]);
path.pop();
}
}
dfs(0, target);
return result;
}Walkthrough
candidates = [2,3,6,7], target = 7. Paths: [7], [2,2,3].
Invariant: Loop starts at start so [2,3] and [3,2] are not both generated.
Edge Cases
- Exact hit with single coin
- No solution → empty list
- Large target with small coins — depth bounded by
target/min
Pitfalls
- Advancing
startbyindex+1— that forbids reuse (wrong for this problem) - Not copying
pathwhen recording answer
Similar Problems
- 40. Combination Sum II — no reuse; dedupe on sorted input
- 78. Subsets — same DFS buildup pattern
- 322. Coin Change — minimum coins (optimization sibling)
Variants
- Coin count limit — cap how many times each value may be used.
- Negative candidates — sort + prune; watch for non-termination.
Mind-Map Tags
#backtracking #combination #dfs #reuse-allowed #sorted-start-index
Mark this page when you finish learning it.
Last updated on
Spotted something unclear or wrong on this page?