Provided with a goal integer target and an array of unique integer candidates, provide a list of all possible combinations of candidates in which the selected numbers add up to the target. The combinations can be returned in any order.
A candidate may be selected from the pool an infinite number of times. There are two distinct combinations if the frequency if at least one of the selected figures differs.
The test cases are created so that, for the given input, there are fewer than 150 possible combinations that add up to the target.
If there is no possible subsequences then return empty vector.
Input : candidates = [2, 3, 5, 4] , target = 7
Output : [ [2, 2, 3] , [3, 4] , [5, 2] ]
Explanation :
2 and 3 are candidates, and 2 + 2 + 3 = 7. Note that 2 can be used multiple times.
5 and 2 are candidates, and 5 + 2 = 7.
3 and 4 are candidates, and 3 + 4 = 7.
There are total three combinations.
Input : candidates = [2], target = 1
Output : []
Explanation : There is no way we can choose the candidates to sum up to target.
Input : candidates = [1], target = 1
To understand the problem a bit better, visualize organizing a fundraising event with various items available for purchase, each contributing a different amount to the total donation. The goal is to find all combinations of these items that exactly meet a specific donation target. To achieve this, one would start by selecting an item and adding its contribution to the running total. If this total reaches the target, the current combination is recorded. If it exceeds the target, or if there are no more items to consider, the process must go back and try different combinations of remaining items. This method mirrors recursion, where every decision branches into further possibilities until the target is reached or all options are exhausted.
The core intuition behind this problem involves exploring all possible subsets of a given set of numbers to find combinations that sum up to a specific target value. Starting with the entire set, the approach involves making a series of choices: include or exclude each number in the current subset. By recursively exploring these choices, one generates all potential combinations. If a combination meets the target sum, it is recorded. If it exceeds the target or no further numbers are available, the process reverts to previous choices to explore other possible subsets. This systematic exploration of decisions and re-evaluation of choices mirrors recursion, where each step involves exploring different paths until all possibilities are exhausted.
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
// Recursive function to find all subsequences with the given target sum
void func(vector<int>& v, int i, int sum, vector<int>& v2, vector<vector<int>>& ans) {
// Base case: if the sum is zero, add the current subsequence to the result
if (sum == 0) {
ans.push_back(v2);
return;
}
// Base case: if the sum becomes negative
if (sum < 0) {
return;
}
// Base case: if no elements are left
if (i < 0) {
return;
}
// Exclude the current element and move to the next
func(v, i - 1, sum, v2, ans);
// Include the current element in the subsequence
v2.push_back(v[i]);
// Recursively call the function with the included element
func(v, i, sum - v[i], v2, ans);
// Backtrack by removing the last added element
v2.pop_back();
}
// Main function to find all unique combinations of candidates that sum to the target
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> ans;
vector<int> v;
// Start the recursive process
func(candidates, candidates.size() - 1, target, v, ans);
return ans;
}
};
// Main function to test the solution
int main() {
Solution sol;
vector<int> candidates = {2, 3, 6, 7};
int target = 7;
vector<vector<int>> result = sol.combinationSum(candidates, target);
for (const auto& combination : result) {
for (int num : combination) {
cout << num << " ";
}
cout << endl;
}
return 0;
}
import java.util.ArrayList;
import java.util.List;
class Solution {
// Recursive function to find all subsequences with the given target sum
public void func(List<Integer> v, int i, int sum, List<Integer> v2, List<List<Integer>> ans) {
// Base case: if the sum is zero, add the current subsequence to the result
if (sum == 0) {
ans.add(new ArrayList<>(v2));
return;
}
// Base case: if the sum becomes negative or no elements are left
if (sum < 0 || i < 0) {
return;
}
// Exclude the current element and move to the next
func(v, i - 1, sum, v2, ans);
// Include the current element in the subsequence
v2.add(v.get(i));
// Recursively call the function with the included element
func(v, i, sum - v.get(i), v2, ans);
// Backtrack by removing the last added element
v2.remove(v2.size() - 1);
}
// Main function to find all unique combinations of candidates that sum to the target
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> ans = new ArrayList<>();
List<Integer> v = new ArrayList<>();
// Convert the array to a list for easier manipulation
for (int num : candidates) {
v.add(num);
}
// Start the recursive process
func(v, v.size() - 1, target, new ArrayList<>(), ans);
return ans;
}
// Main method to test the solution
public static void main(String[] args) {
Solution sol = new Solution();
int[] candidates = {2, 3, 6, 7};
int target = 7;
List<List<Integer>> result = sol.combinationSum(candidates, target);
System.out.println(result);
}
}
class Solution:
# Recursive function to find all subsequences with the given target sum
def func(self, v, i, sum, v2, ans):
# Base case: if the sum is zero, add the current subsequence to the result
if sum == 0:
ans.append(v2[:])
return
# Base case: if the sum becomes negative or no elements are left
if sum < 0 or i < 0:
return
# Exclude the current element and move to the next
self.func(v, i - 1, sum, v2, ans)
# Include the current element in the subsequence
v2.append(v[i])
# Recursively call the function with the included element
self.func(v, i, sum - v[i], v2, ans)
# Backtrack by removing the last added element
v2.pop()
# Main function to find all unique combinations of candidates that sum to the target
def combinationSum(self, candidates, target):
ans = []
# Create a copy of the candidates list for easier manipulation
v = candidates[:]
# Start the recursive process
self.func(v, len(v) - 1, target, [], ans)
return ans
# Main block to test the solution
if __name__ == "__main__":
sol = Solution()
candidates = [2, 3, 6, 7]
target = 7
result = sol.combinationSum(candidates, target)
print(result)
class Solution {
// Recursive function to find all subsequences with the given target sum
func(v, i, sum, v2, ans) {
// Base case: if the sum is zero, add the current subsequence to the result
if (sum === 0) {
ans.push([...v2]);
return;
}
// Base case: if the sum becomes negative or no elements are left
if (sum < 0 || i < 0) {
return;
}
// Exclude the current element and move to the next
this.func(v, i - 1, sum, v2, ans);
// Include the current element in the subsequence
v2.push(v[i]);
// Recursively call the function with the included element
this.func(v, i, sum - v[i], v2, ans);
// Backtrack by removing the last added element
v2.pop();
}
// Main function to find all unique combinations of candidates that sum to the target
combinationSum(candidates, target) {
const ans = [];
const v2 = [];
// Start the recursive process
this.func(candidates, candidates.length - 1, target, v2, ans);
return ans;
}
}
// Main block to test the solution
const sol = new Solution();
const candidates = [2, 3, 6, 7];
const target = 7;
const result = sol.combinationSum(candidates, target);
console.log(result);
Time Complexity : The time complexity is O(K * 2^N), where N is the number of elements, due to the exponential number of possible combinations explored in the worst case. For each subset, it may take up to K operations to process, where K is the maximum length of any subset in the result
Time Complexity : The space complexity is O(k * N), where N is the maximum depth of recursion, which corresponds to the length of the combination path stored in memory.
Q: How do you handle unlimited usage of a candidate?
A: In backtracking, allow the recursive call to revisit the same index to include the current candidate multiple times. Move to the next index only when the candidate is skipped.
Q: How does sorting the candidates help?
A: Sorting ensures that candidates are processed in ascending order. This allows early termination of branches when the target becomes smaller than the current candidate.
Q: What if candidates can only be used once?
A: Modify the recursive function to move to the next index after including the current candidate. This prevents reusing a candidate multiple times.
Q: How would you handle large values of target?
A: For large targets, reduce the problem size using memoization to cache results for previously computed targets. This avoids redundant computations.