Combination Sum medium
Problem Statement
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.
It's guaranteed that the number of unique combinations is less than 150.
Example 1:
Input: candidates = [2,3,6,7], target = 7
Output: [[2,2,3],[7]]
Explanation:
2 and 3 are candidates. 2 + 2 + 3 = 7. 7 is a candidate. Hence the result.
Example 2:
Input: candidates = [2,3,5], target = 8
Output: [[2,2,2,2],[2,3,3],[3,5]]
Explanation:
2, 3 and 5 are candidates. 2 + 2 + 2 + 2 = 8. 2 + 3 + 3 = 8. 3 + 5 = 8. Hence the result.
Steps
- Backtracking: We will use a backtracking approach to explore all possible combinations.
- Recursive Function: A recursive function will explore combinations. It takes the current index, the current combination, and the remaining target sum as input.
- Base Cases:
- If the remaining
target
is 0, we've found a valid combination; add it to the result and return. - If the remaining
target
is negative or we've reached the end of thecandidates
array, we backtrack (return).
- If the remaining
- Recursive Step: For each candidate at the current index, we:
- Add the candidate to the current combination.
- Recursively call the function with the updated combination and remaining target.
- Remove the candidate from the current combination (backtracking) to explore other possibilities.
Explanation
The backtracking algorithm systematically explores all possible combinations by making choices (adding candidates) and undoing those choices (removing candidates). The recursive calls branch out to explore different possibilities, and once a combination sums to the target, it's added to the result. The use of vector<int>
for combination
makes it easy to build up and modify the current combination during the recursive calls. The removal of the last element during backtracking ensures that we don't overcount combinations and maintain uniqueness
Code
#include <vector>
class Solution {
public:
std::vector<std::vector<int>> combinationSum(std::vector<int>& candidates, int target) {
std::vector<std::vector<int>> result;
std::vector<int> combination;
backtrack(candidates, target, 0, combination, result);
return result;
}
private:
void backtrack(const std::vector<int>& candidates, int target, int index, std::vector<int>& combination, std::vector<std::vector<int>>& result) {
if (target == 0) {
result.push_back(combination);
return;
}
if (target < 0 || index >= candidates.size()) {
return;
}
// Include the current candidate
combination.push_back(candidates[index]);
backtrack(candidates, target - candidates[index], index, combination, result); // Explore with the same candidate again
// Exclude the current candidate (backtracking)
combination.pop_back();
backtrack(candidates, target, index + 1, combination, result); //Explore with the next candidate
}
};
Complexity
- Time Complexity: O(N^T), where N is the number of candidates and T is the target value. In the worst case, we might explore all possible combinations.
- Space Complexity: O(T) in the worst case, due to the recursive call stack and the maximum depth of the recursion, which is proportional to the target value. The space used to store the result is also proportional to the number of combinations found which is bounded by a small constant (less than 150) as stated in the problem description.