Given a collection of candidate numbers candidates
(which may contain duplicates) and a target number target
, find all unique combinations in candidates
where the candidate numbers sum to target
.
candidates
may only be used once in the combination.
Example:
Input: candidates = [10,1,2,7,6,1,5]
, target = 8
Output: [[1,1,6],[1,2,5],[1,7],[2,6]]
Constraints:
candidates.length
≤ 100candidates[i]
≤ 50target
≤ 30The problem asks us to find all unique combinations of numbers from a list that add up to a given target, with the restriction that each number may be used at most once per combination. Since the input may contain duplicate numbers, we must avoid returning duplicate combinations.
A brute-force approach would be to generate all possible subsets and filter those that sum up to the target, but this is inefficient and may produce duplicate results if the input has repeated numbers.
To optimize, we can:
Let's break down the solution step by step:
candidates
helps us easily identify and skip duplicates during backtracking.
start
index to ensure each element is only used once per combination.target
, add it to the result.target
, stop exploring this path (prune the branch).candidates
starting from start
index.This method ensures that:
Let's use candidates = [10,1,2,7,6,1,5]
and target = 8
as an example.
Notice how we skip the second '1' at the same recursion depth to avoid duplicate combinations.
Brute-force:
Generating all possible subsets of n
elements is O(2^n), and checking the sum for each subset is O(n), so total is O(n*2^n). Additionally, we would need to filter out duplicates, which adds further overhead.
Optimized Backtracking:
The number of unique combinations is less than all possible subsets, but in the worst case (with all small numbers and a large target), the algorithm still explores many paths, so time complexity is O(2^n) in the worst case. However, pruning (stopping when the sum exceeds target) and skipping duplicates significantly reduces the practical number of recursive calls.
Space complexity is O(n) for the recursion stack and combination storage, plus O(k) for the total number of valid combinations returned (where k is the number of solutions).
In this problem, we efficiently find all unique combinations that sum up to a target by using backtracking with careful duplicate skipping. Sorting the candidates enables us to avoid redundant work and ensures each combination is unique. By pruning impossible paths early and only exploring valid options, we achieve both correctness and efficiency. The elegance of the solution lies in its clarity and the way it leverages sorting and recursion to manage constraints.
class Solution:
def combinationSum2(self, candidates, target):
candidates.sort()
res = []
def backtrack(start, path, total):
if total == target:
res.append(path[:])
return
if total > target:
return
prev = -1
for i in range(start, len(candidates)):
if candidates[i] == prev:
continue
path.append(candidates[i])
backtrack(i + 1, path, total + candidates[i])
path.pop()
prev = candidates[i]
backtrack(0, [], 0)
return res
class Solution {
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
vector<vector<int>> res;
vector<int> path;
backtrack(candidates, target, 0, path, res);
return res;
}
void backtrack(vector<int>& candidates, int target, int start, vector<int>& path, vector<vector<int>>& res) {
if (target == 0) {
res.push_back(path);
return;
}
if (target < 0) return;
int prev = -1;
for (int i = start; i < candidates.size(); ++i) {
if (candidates[i] == prev) continue;
path.push_back(candidates[i]);
backtrack(candidates, target - candidates[i], i + 1, path, res);
path.pop_back();
prev = candidates[i];
}
}
};
import java.util.*;
class Solution {
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
List<List<Integer>> res = new ArrayList<>();
backtrack(candidates, target, 0, new ArrayList<>(), res);
return res;
}
private void backtrack(int[] candidates, int target, int start, List<Integer> path, List<List<Integer>> res) {
if (target == 0) {
res.add(new ArrayList<>(path));
return;
}
if (target < 0) return;
int prev = -1;
for (int i = start; i < candidates.length; i++) {
if (candidates[i] == prev) continue;
path.add(candidates[i]);
backtrack(candidates, target - candidates[i], i + 1, path, res);
path.remove(path.size() - 1);
prev = candidates[i];
}
}
}
var combinationSum2 = function(candidates, target) {
candidates.sort((a, b) => a - b);
const res = [];
function backtrack(start, path, total) {
if (total === target) {
res.push([...path]);
return;
}
if (total > target) return;
let prev = -1;
for (let i = start; i < candidates.length; i++) {
if (candidates[i] === prev) continue;
path.push(candidates[i]);
backtrack(i + 1, path, total + candidates[i]);
path.pop();
prev = candidates[i];
}
}
backtrack(0, [], 0);
return res;
};