NeetCode #489LC-39MediumBacktrackingBlind 75NC 150NC 250
← Back to All Problems#489 · #39 · Combination Sum(组合总和)
📌 Problem Statement & Constraints
Given an array of distinct integers
candidates and a target, return all unique combinations that sum to the target. Each candidate may be used unlimited times. Constraints: 1 <= candidates.length <= 30, 2 <= candidates[i] <= 40, 1 <= target <= 40.💡 Core Algorithmic Approaches
- Backtrack with two decisions per index: skip it, or take it and stay at the same index (allowing reuse).
- Staying at the same index is what enables unlimited reuse.
- Prune when the remaining target goes negative.
- Because the candidates are distinct, no deduplication is needed.
💻 Benchmark Python3 Implementation
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
res = []
def dfs(i: int, path: List[int], remain: int) -> None:
if remain == 0:
res.append(path[:]) # a valid combination
return
if i == len(candidates) or remain < 0:
return
dfs(i + 1, path, remain) # skip candidates[i]
path.append(candidates[i])
dfs(i, path, remain - candidates[i]) # reuse candidates[i]
path.pop()
dfs(0, [], target)
return res
# Loop-based formulation: only move forward, so combinations are naturally unique
class Solution2:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
res = []
def dfs(start: int, path: List[int], remain: int) -> None:
if remain == 0:
res.append(path[:])
return
for i in range(start, len(candidates)):
if candidates[i] > remain:
continue # prune (valid only after sorting)
path.append(candidates[i])
dfs(i, path, remain - candidates[i]) # i, not i+1 -> reuse
path.pop()
candidates.sort()
dfs(0, [], target)
return res⚡ Complexity Deep Dive
⏱️ Time Complexity
O(n^(target/min)) in the worst case, bounded by the number of valid combinations.
💾 Space Complexity
O(target/min) for the recursion depth.
⚠️ Interview Pitfalls & Follow-ups
- Advancing to
i + 1when taking a candidate: reuse would be impossible. - Not pruning on
remain < 0: the recursion would explore useless branches (and loop forever if no candidate is positive). - Using a set of sorted tuples for deduplication: unnecessary since the candidates are distinct and the loop only moves forward.
- Forgetting
path.pop(): the path would accumulate across branches.