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
. You may return the combinations in any order.
The same number may be chosen from candidates
an unlimited number of times. Two combinations are unique if the frequency of at least one of the chosen numbers is different.
The test cases are generated such that the number of unique combinations that sum up to target
is less than 150
combinations for the given input.
Example 1:
Input: candidates = [2,3,6,7], target = 7 Output: [[2,2,3],[7]] Explanation: 2 and 3 are candidates, and 2 + 2 + 3 = 7. Note that 2 can be used multiple times. 7 is a candidate, and 7 = 7. These are the only two combinations.
Example 2:
Input: candidates = [2,3,5], target = 8 Output: [[2,2,2,2],[2,3,3],[3,5]]
Example 3:
Input: candidates = [2], target = 1 Output: []
Constraints:
1 <= candidates.length <= 30
2 <= candidates[i] <= 40
candidates
are distinct.1 <= target <= 40
Given a set of distinct positive integers candidates
and a target integer target
, find all unique combinations in candidates
where the chosen numbers sum to target
. Each number in candidates
may be chosen an unlimited number of times.
The most efficient approach is backtracking, enhanced with pruning to avoid unnecessary computations.
Sorting: Sort the candidates
array. This allows for efficient pruning because if the current sum exceeds the target, we can stop exploring further combinations involving larger numbers.
Backtracking Function: The core logic lies in a recursive backtracking function. This function takes:
index
: The current index in the candidates
array.remainingSum
: The remaining sum needed to reach the target
.combination
: The current combination being built.result
: A list to store all valid combinations.Base Cases:
remainingSum
is 0, a valid combination is found, so add combination
to result
.remainingSum
is negative or index
is out of bounds, this branch is invalid, so return.Recursive Step: For each element at the current index
, explore two possibilities:
combination
, recursively call the backtracking function with the updated remainingSum
and the same index
(allowing the same element to be chosen multiple times), and then remove the element from combination
(backtracking).remainingSum
and the next index
(moving to the next element).Initial Call: The backtracking function is initially called with index = 0
, remainingSum = target
, an empty combination
, and an empty result
list.
Time Complexity: O(N * 2T), where N is the length of candidates
and T is the target
. In the worst case, each number might be included or excluded in every combination. However, the sorting and pruning significantly reduce the actual runtime in many cases.
Space Complexity: O(T) in the worst case, due to the maximum depth of the recursion stack and the space required to store the largest possible combination.
def combinationSum(candidates, target):
result = []
candidates.sort() # Sort for efficient pruning
def backtrack(index, remainingSum, combination):
if remainingSum == 0:
result.append(combination.copy()) # Found a valid combination
return
if remainingSum < 0 or index >= len(candidates):
return # Invalid branch
# Include the current element
combination.append(candidates[index])
backtrack(index, remainingSum - candidates[index], combination)
combination.pop() # Backtrack
# Exclude the current element
backtrack(index + 1, remainingSum, combination)
backtrack(0, target, [])
return result
The implementations in other languages (Java, C++, Go, TypeScript, Rust, C#, PHP) will follow a very similar structure, differing only in syntax and data structure specifics. The core backtracking algorithm remains the same.