You are given an array of distinct integers nums and a target integer target. Your task is to return a list of all unique combinations of nums where the chosen numbers sum to target.
The same number may be chosen from nums an unlimited number of times. Two combinations are the same if the frequency of each of the chosen numbers is the same, otherwise they are different.
You may return the combinations in any order and the order of the numbers in each combination can be in any order.
Example 1:
Input:
nums = [2,5,6,9]
target = 9
Output: [[2,2,5],[9]]Explanation:
2 + 2 + 5 = 9. We use 2 twice, and 5 once.
9 = 9. We use 9 once.
Example 2:
Input:
nums = [3,4,5]
target = 16
Output: [[3,3,3,3,4],[3,3,5,5],[4,4,4,4],[3,4,4,5]]Example 3:
Input:
nums = [3]
target = 5
Output: []Constraints:
nums are distinct.1 <= nums.length <= 202 <= nums[i] <= 302 <= target <= 30
You should aim for a solution with O(2^(t/m)) time and O(t/m) space, where t is the given target and m is the minimum value in the given array.
Can you think of this problem in terms of a decision tree, where at each step, we have n decisions, where n is the size of the array? In this decision tree, we can observe that different combinations of paths are formed. Can you think of a base condition to stop extending a path? Maybe you should consider the target value.
We can use backtracking to recursively traverse these paths and make decisions to choose an element at each step. We maintain a variable sum, which represents the sum of all the elements chosen in the current path. We stop this recursive path if sum == target, and add a copy of the chosen elements to the result. How do you implement it?
We recursively traverse the array starting from index i. At each step, we select an element from i to the end of the array. We extend the recursive path with elements where sum <= target after including that element. This creates multiple recursive paths, and we append the current list to the result whenever the base condition is met.
Before attempting this problem, you should be comfortable with:
We want to build all combinations of numbers that add up to the target.
Each number can be used multiple times, so at every index we have two choices:
We explore all possible choices using backtracking.
Whenever the running total equals the target, we store that combination.
If the total becomes greater than the target or we run out of numbers, we stop exploring that path.
Define a recursive function dfs(i, currentList, total) where:
i is the current index in numscurrentList is the current combination being builttotal is the sum of numbers in currentListIf total == target, add a copy of currentList to the result and return.
If i goes out of bounds or total exceeds the target, return (stop exploring).
Choose to include nums[i]:
nums[i] to currentListdfs(i, currentList, total + nums[i]) (stay at same index)nums[i] (backtrack)Choose to skip nums[i]:
dfs(i + 1, currentList, total)Start with dfs(0, [], 0) and return the result list.
class Solution:
def combinationSum(self, nums: List[int], target: int) -> List[List[int]]:
res = []
def dfs(i, cur, total):
if total == target:
res.append(cur.copy())
return
if i >= len(nums) or total > target:
return
cur.append(nums[i])
dfs(i, cur, total + nums[i])
cur.pop()
dfs(i + 1, cur, total)
dfs(0, [], 0)
return resWhere is the given and is the minimum value in .
This optimized backtracking solution avoids exploring useless paths by using sorting + early stopping.
i, allowing reuse of the same number.Sorting + pruning significantly reduces unnecessary recursion.
nums so we can stop early when the sum exceeds the target.dfs(i, currentList, total):total == target:currentList to the result.j from i to end of list:total + nums[j] > target, stop the loop (no need to check larger numbers).nums[j] to currentList.dfs(j, currentList, total + nums[j]) (reuse allowed).dfs(0, [], 0) and return the result.This ensures we explore only valid combinations while eliminating unnecessary work.
class Solution:
def combinationSum(self, nums: List[int], target: int) -> List[List[int]]:
res = []
nums.sort()
def dfs(i, cur, total):
if total == target:
res.append(cur.copy())
return
for j in range(i, len(nums)):
if total + nums[j] > target:
return
cur.append(nums[j])
dfs(j, cur, total + nums[j])
cur.pop()
dfs(0, [], 0)
return resWhere is the given and is the minimum value in .
Since each number can be used unlimited times, after including nums[i], you should stay at index i, not move to i + 1. Moving forward prevents reusing the same element.
# Wrong: can't reuse the same number
cur.append(nums[i])
dfs(i + 1, cur, total + nums[i])
# Correct: stay at same index to allow reuse
cur.append(nums[i])
dfs(i, cur, total + nums[i])Without maintaining an index, you might generate [2,3] and [3,2] as separate combinations. Always iterate from the current index forward, never backward, to ensure each combination is generated only once in sorted order.
Continuing recursion when total > target wastes time exploring invalid paths. With sorted input, you can break early once total + nums[j] > target since all subsequent numbers are larger.
# Inefficient: explores all paths
for j in range(i, len(nums)):
dfs(j, cur, total + nums[j])
# Optimized: stop early when sum exceeds target
for j in range(i, len(nums)):
if total + nums[j] > target:
return
dfs(j, cur, total + nums[j])