You are given an array of integers candidates, which may contain duplicates, and a target integer target. Your task is to return a list of all unique combinations of candidates where the chosen numbers sum to target.
Each element from candidates may be chosen at most once within a combination. The solution set must not contain duplicate combinations.
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: candidates = [9,2,2,4,6,1,5], target = 8
Output: [
[1,2,5],
[2,2,4],
[2,6]
]Example 2:
Input: candidates = [1,2,3,4,5], target = 7
Output: [
[1,2,4],
[2,5],
[3,4]
]Constraints:
1 <= candidates.length <= 1001 <= candidates[i] <= 501 <= target <= 30
You should aim for a solution with O(n * (2^n)) time and O(n) space, where n is the size of the input array.
A brute-force solution would be to create a hash set, which is used to detect duplicates, to get combinations without duplicates. Can you think of a better way without using a hash set? Maybe you should sort the input array and observe the recursive calls that are responsible for duplicate combinations.
We can use backtracking to generate all combinations whose sum equals the given target. When the input array contains duplicate elements, it may result in duplicate combinations. To avoid this, we can sort the array. Why does sorting help? Because as we traverse the array from left to right, we form combinations with the current element. By skipping duplicate elements, we ensure that the same combinations are not repeated for identical elements. How can you implement this?
We recursively traverse the given array starting at index i, with a variable sum representing the sum of the picked elements. We explore elements from i to the end of the array and extend the recursive path if adding the current element results in sum <= target. When we processed an element, we backtrack and proceed to the next distinct element, skipping any similar elements in the middle to avoid duplicate combinations.
Before attempting this problem, you should be comfortable with:
The brute-force approach tries every possible subset of the candidate numbers.
This method is easy to understand but slow because it explores all subsets, even invalid or duplicate ones.
dfs(i, currentList, total):total == target, add the tuple version of currentList to a set.total > target or i == len(candidates), stop exploring.i:currentList, recurse with i + 1, then remove it.i + 1.class Solution:
def combinationSum2(self, candidates, target):
res = set()
candidates.sort()
def generate_subsets(i, cur, total):
if total == target:
res.add(tuple(cur))
return
if total > target or i == len(candidates):
return
cur.append(candidates[i])
generate_subsets(i + 1, cur, total + candidates[i])
cur.pop()
generate_subsets(i + 1, cur, total)
generate_subsets(0, [], 0)
return [list(combination) for combination in res]The goal is to choose numbers that sum to the target, but each number can be used once, and the list may contain duplicates.
To avoid generating duplicate combinations, we:
[1,2,2] multiple times.Sorting + skipping duplicates + backtracking ensures we only build valid and unique combinations.
candidates.dfs(i, cur, total):total == target, add a copy of cur to the result.total > target or i == len(candidates), stop exploring.candidates[i] to cur.i + 1.i forward while the next number is the same.dfs(i + 1, cur, total) after skipping duplicates.class Solution:
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
res = []
candidates.sort()
def dfs(i, cur, total):
if total == target:
res.append(cur.copy())
return
if total > target or i == len(candidates):
return
cur.append(candidates[i])
dfs(i + 1, cur, total + candidates[i])
cur.pop()
while i + 1 < len(candidates) and candidates[i] == candidates[i+1]:
i += 1
dfs(i + 1, cur, total)
dfs(0, [], 0)
return resInstead of sorting and skipping duplicates, this method uses a frequency map that stores how many times each number appears.
Example:
If input is [1,1,2,2,2,3], we convert it into:
[1, 2, 3]{1: 2, 2: 3, 3: 1}Now each number can be chosen up to its allowed count, and we explore combinations using backtracking.
This avoids duplicates because we never pick the same number more times than it appears.
At each index i (pointing to unique numbers):
We stop exploring a path when:
target == 0 → we found a valid combinationtarget < 0 or i == len(nums) → invalid pathThis ensures we explore all valid combinations while preventing duplicates naturally.
count[num]++) for all numbers.A).backtrack(i, target, cur):target == 0, add cur to the result.target < 0 or i is out of bounds, return.nums[i] if available in frequency map:curi (because duplicates allowed up to frequency)nums[i]:i + 1class Solution:
def combinationSum2(self, nums, target):
self.res = []
self.count = defaultdict(int)
cur = []
A = []
for num in nums:
if self.count[num] == 0:
A.append(num)
self.count[num] += 1
self.backtrack(A, target, cur, 0)
return self.res
def backtrack(self, nums, target, cur, i):
if target == 0:
self.res.append(cur.copy())
return
if target < 0 or i >= len(nums):
return
if self.count[nums[i]] > 0:
cur.append(nums[i])
self.count[nums[i]] -= 1
self.backtrack(nums, target - nums[i], cur, i)
self.count[nums[i]] += 1
cur.pop()
self.backtrack(nums, target, cur, i + 1)We need all unique combinations where each number can be used at most once, and duplicates in the input should not create duplicate combinations.
To handle duplicates safely, we:
Sort the array
This groups equal numbers together, which helps us skip duplicates easily.
Use backtracking where at each index we decide:
To avoid duplicate combinations:
candidates[i] == candidates[i - 1] and we are still in the same level of recursion (i > idx),We stop early if current_sum + candidates[i] > target because the list is sorted.
This approach explores each number only once per combination path and guarantees no repeated results.
dfs(idx, path, curSum):curSum == target, add a copy of path to the result.i from idx to end:i > idx and the current number equals the previous → skip (duplicate control).target → break (pruning).i + 1 (cannot reuse same element).dfs(0, [], 0) and return the result.class Solution:
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
res = []
candidates.sort()
def dfs(idx, path, cur):
if cur == target:
res.append(path.copy())
return
for i in range(idx, len(candidates)):
if i > idx and candidates[i] == candidates[i - 1]:
continue
if cur + candidates[i] > target:
break
path.append(candidates[i])
dfs(i + 1, path, cur + candidates[i])
path.pop()
dfs(0, [], 0)
return resThe input contains duplicates, and each element can only be used once. Without proper duplicate handling, [1,1,2] with target 3 might produce [1,2] twice. Always sort first and skip consecutive duplicates at the same recursion level.
# Wrong: generates duplicates
for i in range(idx, len(candidates)):
dfs(i + 1, ...)
# Correct: skip duplicates at same level
for i in range(idx, len(candidates)):
if i > idx and candidates[i] == candidates[i - 1]:
continue
dfs(i + 1, ...)Unlike Combination Sum I where elements can be reused, this problem requires each element to be used at most once. Recursing with the same index allows reuse.
# Wrong: allows reusing same element
dfs(i, cur, total + candidates[i])
# Correct: move to next element
dfs(i + 1, cur, total + candidates[i])The duplicate-skipping logic candidates[i] == candidates[i-1] only works on a sorted array. Without sorting, duplicates won't be adjacent and the skip logic fails silently, producing duplicate combinations.