Skip to content

Combination Sum

Given an array of distinct integers candidates and a target integer target, return a list of all unique combinations of candidates where the chosen numbers sum to target.

Key constraint: The same number in candidates may be chosen an unlimited number of times. The combination itself must be unique (different orderings of the same numbers count as one).

InputTargetOutputExplanation
candidates = [2,3,6,7]7[[2,2,3], [7]]2+2+3=7, and 7=7
candidates = [2,3,5]8[[2,2,2,2], [2,3,3], [3,5]]Multiple ways to reach 8
candidates = [2]1[]No combination sums to 1
  • 1 <= candidates.length <= 30
  • 2 <= candidates[i] <= 40
  • All elements are unique
  • 1 <= target <= 500
ApproachTimeSpaceOptimizationBest When
Basic BacktrackingO(N^T)O(T) recursionNoneWant a straightforward, clear solution
Optimized with PruningO(N^T)O(T) recursionSort + early breakExpect large targets or large candidate arrays
  • Pick Basic if you want to understand the core backtracking logic.
  • Pick Optimized if you want production-ready code with early termination.
Clear Logic
Basic
O(N^T) time · O(T) space
Optimized
With Pruning
O(N^T) time · O(T) space
✓ Simple

Recursively explore all possible combinations by trying each candidate at each step. When exploring a candidate, we can choose it again (pass i instead of i+1), allowing unlimited reuse. Stop when the remaining sum hits zero (found solution) or goes negative (prune branch).

The key difference from standard combinations is the i instead of i+1 parameter—this allows the same element to be used multiple times.

⏱ Time O(N^T) Exponential branching 💾 Space O(T) Recursion depth

Input: candidates = [2,3,6,7], target = 7

DepthCurrentRemainingAction
0[]7Try 2: choose 2
1[2]5Try 2: choose 2 again
2[2,2]3Try 2: remaining < 0, backtrack; Try 3: choose 3
3[2,2,3]0Found! Save [2,2,3]
0[]7Continue: Try 3, Try 6, Try 7
1[7]0Found! Save [7]
function combinationSumBasic(candidates, target):
result = []
function backtrack(index, current, remaining):
if remaining == 0:
result.append(copy of current)
return
if remaining < 0:
return
for i from index to len(candidates)-1:
candidate = candidates[i]
current.append(candidate)
backtrack(i, current, remaining - candidate) # i, not i+1
current.pop()
backtrack(0, [], target)
return result
combination_sum_basic.py
from typing import List
def combination_sum_basic(candidates: List[int], target: int) -> List[List[int]]:
"""
Find all unique combinations that sum to target using backtracking.
Time: O(N^T), Space: O(T) for recursion depth
"""
result = []
def backtrack(index: int, current: List[int], remaining: int) -> None:
# Base case: found a valid combination
if remaining == 0:
result.append(current[:])
return
# Base case: no valid combinations possible
if remaining < 0:
return
# Explore: try each candidate from index onwards
for i in range(index, len(candidates)):
candidate = candidates[i]
# Choose
current.append(candidate)
# Explore: can reuse the same candidate (i, not i+1)
backtrack(i, current, remaining - candidate)
# Unchoose
current.pop()
backtrack(0, [], target)
return result
print(combination_sum_basic([2, 3, 6, 7], 7))
# Output: [[2,2,3], [7]]

Approach 2: Optimized Backtracking with Pruning

Section titled “Approach 2: Optimized Backtracking with Pruning”
★ Recommended

Improve the basic approach by sorting the candidates first. Then, during recursion, if the current candidate exceeds the remaining sum, break immediately because all subsequent candidates (being larger) will also exceed it.

This pruning significantly reduces the search space for large targets or large candidate lists, though the worst-case time complexity remains O(N^T).

⏱ Time O(N^T) Exponential, but pruned 💾 Space O(T) Recursion depth

Input: candidates = [2,3,6,7] (sorted), target = 7

DepthCurrentRemainingAction
0[]7Try 2
1[2]5Try 2 again
2[2,2]3Try 2: candidate > remaining? No. Try 3: choose 3
3[2,2,3]0Found! Save [2,2,3]
2[2,2]3Try 6: 6 > 3? Yes, break (pruning) ✓
function combinationSumOptimized(candidates, target):
candidates.sort()
result = []
function backtrack(index, current, remaining):
if remaining == 0:
result.append(copy of current)
return
for i from index to len(candidates)-1:
candidate = candidates[i]
if candidate > remaining:
break # Pruning: all further candidates too large
current.append(candidate)
backtrack(i, current, remaining - candidate) # i, not i+1
current.pop()
backtrack(0, [], target)
return result
combination_sum_optimized.py
from typing import List
def combination_sum_optimized(candidates: List[int], target: int) -> List[List[int]]:
"""
Find all unique combinations that sum to target using optimized backtracking.
Optimization: skip candidates that are too large.
Time: O(N^T), Space: O(T) for recursion depth
"""
candidates.sort() # Sort to enable pruning
result = []
def backtrack(index: int, current: List[int], remaining: int) -> None:
# Base case: found a valid combination
if remaining == 0:
result.append(current[:])
return
# Explore: try each candidate from index onwards
for i in range(index, len(candidates)):
candidate = candidates[i]
# Pruning: if candidate > remaining, all further candidates will be too large
if candidate > remaining:
break
# Choose
current.append(candidate)
# Explore: can reuse the same candidate (i, not i+1)
backtrack(i, current, remaining - candidate)
# Unchoose
current.pop()
backtrack(0, [], target)
return result
print(combination_sum_optimized([2, 3, 6, 7], 7))
# Output: [[2,2,3], [7]]
✓ Simple

A third approach demonstrating an alternative technique for solving this problem. This implementation optimizes either time or space complexity compared to the previous approaches.

⏱ Time O(n) Optimized complexity 💾 Space O(1) Efficient space usage
function approach3():
// Implementation approach goes here
combination_sum_space_optimized.py
"""
Solution for Combination Sum
"""
def solve():
"""Implementation for approach 3 of Combination Sum"""
pass
if __name__ == "__main__":
print("Solution ready")