- Input:
candidates— array of positive integers (may contain duplicates), and an integertarget. - Output: All unique combinations of numbers from
candidateswhere the chosen numbers sum totarget. - Constraint: Each element in
candidatesmay be used once in a combination. - Order of combinations in the output does not matter, but each distinct combination (as a multiset) must appear exactly once.
Example:
candidates = [10,1,2,7,6,1,5], target = 8
One valid output:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]
This is a backtracking / DFS problem with two complications:
- Each candidate may be chosen at most once → when you recurse after choosing
arr[i], you start the next recursion ati+1. - Candidates contain duplicates, and you must avoid generating duplicate combination results. Sorting the array first groups equal values together — then, during the loop you skip a candidate if it’s the same as the previous candidate at the same recursive level (
if (i > ind && arr[i] == arr[i-1]) continue;). That ensures each distinct combination is generated exactly once.
Pruning: Because the array is sorted, when arr[i] > target you can break the loop early — all further elements are ≥ arr[i].
A brute-force approach would try all subsets of candidates (there are 2^n of them) and check which ones sum to target, then deduplicate results. That’s inefficient — generating duplicates and doing heavy post-processing.
The sorted+pruned backtracking approach avoids generating invalid or duplicate combinations in the first place.
-
Sort
candidatesascending. -
Start DFS/backtracking from index
0with an empty combinationdsand remainingtarget. -
At recursion level with starting index
ind:-
If
target == 0, pushdsinto results. -
Loop
ifromindtoarr.size()-1:- Skip duplicates at the same level: if
i > indandarr[i] == arr[i-1],continue. - Prune by value: if
arr[i] > target,break. - Choose
arr[i]: push it tods, recurse withi+1(noti) andtarget - arr[i]. - Backtrack: pop the last element.
- Skip duplicates at the same level: if
-
-
Return results.
This ensures:
- Each index is used at most once per path (because recursion moves to
i+1). - Duplicate candidate values only produce combinations where the first occurrence in the group is considered at a given level — duplicates are skipped when they would otherwise replicate the same multiset.
class Solution {
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
vector<vector<int>> res;
vector<int> ds;
combination(0, candidates, target, ds, res);
return res;
}
void combination(int ind, vector<int> &arr, int target, vector<int> &ds, vector<vector<int>> &res) {
if (target == 0) {
res.push_back(ds);
return;
}
for (int i = ind; i < arr.size(); i++) {
if (i > ind && arr[i] == arr[i-1])
continue;
if (arr[i] > target)
break;
ds.push_back(arr[i]);
combination(i + 1, arr, target - arr[i], ds, res);
ds.pop_back();
}
}
};Input: candidates = [2,5,2,1,2], target = 5
-
Sort →
[1,2,2,2,5]. -
Start
ind=0, target=5, ds=[]:-
i=0choose1→ ds=[1], recurseind=1, target=4.-
i=1choose2→ ds=[1,2], recurseind=2, target=2.i=2choose2→ ds=[1,2,2], recurseind=3, target=0→ push[1,2,2]- backtrack
i=3skip becausearr[3]==arr[2]andi>ind
-
backtrack
-
i=2skip duplicate at same level (sincei>indandarr[2]==arr[1]) -
...
-
-
i=1choose2→ ds=[2], recurseind=2, target=3.-
i=2choose2→ ds=[2,2], recurseind=3, target=1.i=32 > target break
-
i=3skip duplicate
-
-
i=4choose5→ ds=[5], recurseind=5, target=0→ push[5]
-
Output: [[1,2,2],[5]].
Note duplicates like [2,1,2] are not produced because of ordering and duplicate skip — combinations are generated in non-decreasing order.
-
Time complexity: Exponential in worst case — generating all unique combinations can be large. A loose bound is
O(2^n)in the worst case, but effective runtime is much smaller due to pruning (arr[i] > target) and skipping duplicates. For typical constraints (LeetCode-like), backtracking is acceptable. -
Space complexity:
- Recursive call stack depth ≤
n(number of candidates) →O(n). - Auxiliary space for current combination
dsisO(n)and for results depends on number and size of found combinations (could be large). - Sorting costs
O(n log n)time andO(1)orO(n)extra space depending on sort implementation.
- Recursive call stack depth ≤
-
Example with duplicates
candidates=[10,1,2,7,6,1,5], target=8Expected output:[[1,1,6],[1,2,5],[1,7],[2,6]](order may vary)
-
Multiple duplicates
candidates=[2,5,2,1,2], target=5→[[1,2,2],[5]]
-
Single candidate
candidates=[1], target=1→[[1]]
-
No solution
candidates=[3,4], target=2→[]
-
All candidates larger than target
candidates=[9,10], target=8→[](quickarr[i] > targetbreak)
-
Empty candidates
candidates=[], target=0→[]or[[]]depending on interpretation — this code returns[](no push unless target==0 at recursion start; if you want[[]]for target==0 you can handle explicitly).
- Sorting is mandatory for the duplicate-skip logic to work correctly.
- Duplicate skip rule:
if (i > ind && arr[i] == arr[i-1]) continue;— this only skips duplicates at the same recursive level. It allows repeated use of the same value at deeper levels when appropriate (but still uses each individual element at most once by advancing the index). i + 1recursion ensures each element is used at most once per combination.- Pruning early:
if (arr[i] > target) break;— thanks to sorting, you can stop the loop early when elements exceed the remaining target.
-
Use
sort(candidates.begin(), candidates.end())— required. -
Pass
candidatesby reference (the code does that). -
If input size is moderate, this straightforward approach is fine. For large inputs:
- Consider small optimizations like reserving vector capacity for
ds. - If you only need a count of combinations (not the combinations themselves), switch to DP.
- Consider small optimizations like reserving vector capacity for
-
Keep combinations in non-decreasing order to make duplicate elimination trivial.
-
When writing tests, include duplicate-heavy inputs and edge cases (empty array, small/large targets).
- Combination Sum I (unlimited use): Similar but you call recursion with
i(stay at same index) when choosing a number (allow repeats). - Combination Sum III: fixed combination size
kand numbers 1..9, choose exactlyknumbers summing ton. - Subset Sum / 0-1 Knapsack: when you only need to know if a subset exists (decision problem) or maximum sum under capacity, dynamic programming is appropriate.
- Allow duplicates across input meaningfully: if input has duplicates but elements are considered distinct (by index), remove the duplicate-skip line; this variant is less common.
Q: Why sort the array?
A: Sorting groups duplicates and enables both the duplicate-skip rule and the early break (
arr[i] > target) pruning.
Q: How does the duplicate skip i > ind && arr[i] == arr[i-1] work?
A: At a given recursion level (fixed
ind), this avoids trying the same value more than once as the first choice at that level. It prevents generating identical combinations that differ only by which duplicate instance was chosen.
Q: Does this algorithm generate combinations in any particular order?
A: Yes — combinations are generated in non-decreasing order (relative to the sorted
candidates) because we only move forward in the array. But the overall list order is determined by recursion traversal and not strictly lexicographic unless you enforce it.
Q: Can the same number appear multiple times in a combination?
A: Only if it appears multiple times in
candidates. Each element incandidatesis used at most once; duplicates in input allow repeated values in a combination but as separate elements. This is the core difference between Combination Sum (I) (unlimited use) and Combination Sum II (single use).
Q: Will this approach miss combinations?
A: No — provided you sorted the array. The approach explores all combinations that respect the "use each index at most once" rule, and the duplicate skipping only avoids duplicate multisets, not unique ones.
Q: Complexity concerns?
A: The algorithm is exponential in the worst case (unavoidable for enumerating all combinations). Sorting costs
O(n log n)extra time.