Start learning free

LESSON 361 OF 568 · BACKTRACKING

Combination Sum II in C++: Backtracking Without Repeats

Practice O(n · 2n) Read the full explanation

Given a vector of integers candidates, which may contain duplicates, and an integer target, return every distinct combination of candidates that adds up to target.

Each candidate may be used at most once in a combination, so a number that appears twice in candidates can appear up to twice in one combination. Two combinations are the same when they hold the same numbers the same number of times, whichever copies of a repeated number they use: in [1,1,7] with target = 8, the first 1 with the 7 and the second 1 with the 7 are one combination, [1,7], and it must appear only once.

Write the numbers of each combination in ascending order, and return the combinations in lexicographic order: compare two combinations number by number, and the first position where they differ decides which one comes first. This is the order in which the search described in the note below finds them. If no combination adds up to target, return an empty vector.

 

Example 1:

Input: candidates = [10,1,2,7,6,1,5], target = 8
Output: [[1,1,6],[1,2,5],[1,7],[2,6]]
Explanation: Sorted, the candidates are [1,1,2,5,6,7,10]. [1,1,6] uses both 1s.
[1,7] can be made with either 1, but it is listed once. [1,2,5] comes before [1,7] because at the second number, 2 is smaller than 7.

Example 2:

Input: candidates = [2,5,2,1,2], target = 5
Output: [[1,2,2],[5]]
Explanation: There are three 2s; [1,2,2] uses two of them and appears once. 5 alone is 5.

Example 3:

Input: candidates = [2,3], target = 4
Output: []
Explanation: 2 + 3 = 5 is too much, and the 2 can't be used twice, so no combination adds up to 4.

 

Constraints:

  • 1 <= candidates.length <= 100
  • 1 <= candidates[i] <= 50
  • 1 <= target <= 30

Note:

The tutorial uses backtracking, as in Combination Sum. Sort candidates. A call gets start, the first index it may still use, remaining, what the combination still needs, and current, the combination built so far. When remaining is 0, it saves a copy of current. Otherwise a loop tries each index i from start on as the next number: it adds candidates[i], calls itself with i + 1 and remaining - candidates[i], and removes the number again. Because the candidates are sorted, the loop can stop at the first one that is bigger than remaining.

Two things change from Combination Sum. First, the deeper call starts at i + 1, not at i: there, a number could be chosen again and again; here, each candidate is used at most once. Second, as in Subsets II, the loop skips candidates[i] when i > start and it equals candidates[i - 1]. Inside one call, every turn of the loop picks a different next number, and a second copy of the same value would build exactly the same combinations again. A copy is still taken when it is the first one this call tries, at i = start: that is how [1,1,6] gets its second 1.

The search is exponential: with n candidates, there are at most 2n ways to pick some of them, and copying a combination takes up to n steps, so the time is O(n · 2n) in the worst case. The sorted order keeps it far smaller in practice: the calls only build distinct combinations whose sum is at most target. Besides the answer, the extra space is O(n): the combination being built and one call per number in it.

STEP 1 OF 14
    Ctrl + Enter to check

    C++ · BACKTRACKING

    Combination Sum Ii in C++, explained

    Problem overview

    The method combinationSum2 receives a vector<int> named candidates, which may contain repeated numbers, and an int target. It returns every distinct combination of candidates that adds up to target, as a vector<vector<int>>. Each candidate can be used at most once, so a number that appears twice in the input can appear up to twice in one combination.

    Two combinations are the same when they hold the same numbers the same number of times, whichever copies they use. With [1,1,7] and a target of 8, the first 1 with the 7 and the second 1 with the 7 are one combination, [1,7], and it is listed once. Each combination is written in ascending order, and the combinations are in lexicographic order: the first position where two of them differ decides. If nothing adds up to the target, the result is an empty vector.

    For candidates = [10,1,2,7,6,1,5] and target = 8 the result is [[1,1,6],[1,2,5],[1,7],[2,6]]. Sorted, the candidates are [1,1,2,5,6,7,10]. [1,2,5] comes before [1,7] because at the second position 2 is smaller than 7. For [2,3] and a target of 4 the result is []: 2 + 3 is 5, and the 2 can't be used twice.

    Approach: sorted backtracking with two pruning rules

    The search builds a combination one number at a time. A call receives start, the first index it may still use, remaining, the amount the combination still needs, and current, the combination so far. When remaining is 0, current adds up to the target and is saved. Otherwise a loop tries each index i from start on as the next number: add candidates[i], call again with i + 1 and remaining - candidates[i], then remove the number so the loop can try the next one.

    Sorting the candidates first pays off three times:

    • Equal numbers sit side by side, so a repeat is spotted by comparing a number with the one before it.
    • Each combination comes out in ascending order, and the combinations come out in lexicographic order, as required.
    • Once a candidate is bigger than remaining, every later one is too, so the loop can stop with break.

    The two rules that make this problem different from one where a number can be reused are these. The recursive call starts at i + 1, so each position is used at most once. And the loop skips candidates[i] when i > start and it equals candidates[i - 1]: inside one call every turn of the loop picks a different next number, and a second copy of a value already tried would rebuild the same combinations. A copy is still used when it is the first one the call tries, at i == start, and that is how [1,1,6] gets its second 1.

    Step by step

    1. Sort candidates with sort(candidates.begin(), candidates.end()).
    2. Create result, a vector<vector<int>> for the combinations found.
    3. Create current, an empty vector<int>: the combination being built.
    4. Start the search at index 0 with all of target still needed.
    5. Return result. The helper added to that same vector.
    6. Declare the helper. backtrack returns void and takes candidates, start, remaining, current and result. The vectors are references, because the helper changes current and result and the caller must see it.
    7. Check for a finished combination. When remaining == 0, the numbers in current add up to exactly target.
    8. Save it and return. Every candidate is at least 1, so adding another number would go past the target.
    9. Loop with i from start to the end of candidates. Starting at start, never at 0, keeps each combination in index order and builds no choice of indexes twice.
    10. Skip repeats: if i > start and candidates[i] == candidates[i - 1], continue.
    11. Stop at a number that is too big: if candidates[i] > remaining, break.
    12. Choose the number with current.push_back(candidates[i]).
    13. Recurse with i + 1 as the new start and remaining - candidates[i] as the amount still needed.
    14. Undo with current.pop_back(), so the loop can try the next candidate in its place.

    The solution in C++

    class Solution {
    public:
        vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
            sort(candidates.begin(), candidates.end());
            vector<vector<int>> result;
            vector<int> current;
            backtrack(candidates, 0, target, current, result);
            return result;
        }
    
        void backtrack(vector<int>& candidates, int start, int remaining, vector<int>& current, vector<vector<int>>& result) {
            if (remaining == 0) {
                result.push_back(current);
                return;
            }
            for (int i = start; i < candidates.size(); i++) {
                if (i > start && candidates[i] == candidates[i - 1]) {
                    continue;
                }
                if (candidates[i] > remaining) {
                    break;
                }
                current.push_back(candidates[i]);
                backtrack(candidates, i + 1, remaining - candidates[i], current, result);
                current.pop_back();
            }
        }
    };
    

    The C++ details worth a closer look:

    • One shared current. It is passed by reference, so the whole search builds and unbuilds a single vector. Each push_back has a matching pop_back, and result.push_back(current) saves a copy of the vector as it is at that moment.
    • continue and break. continue abandons the current turn of the loop and moves to the next i. break leaves the loop altogether. The first is right for a repeated value, because larger numbers may still fit. The second is right for a number that is too big, because the sorted order means every later one is too big as well.
    • candidates is sorted in place. The parameter is a non-const reference, so the caller's vector comes back sorted.
    • Signed and unsigned. candidates.size() is unsigned and i is an int. The comparison is safe for these sizes, and compilers may warn about the mix.

    Complexity

    Time: O(n · 2^n) in the worst case, for n candidates. There are at most 2^n ways to pick some of them, and copying a combination takes up to n steps. In practice the search is far smaller, because it only builds distinct combinations whose sum stays within target.

    The pruning has a visible effect. For 100 candidates equal to 1 and a target of 30, the search makes 31 calls and finds the single combination of thirty 1s. For the numbers 1 to 20 and a target of 30, the search with break makes 1,916 calls and finds 263 combinations. Without the break it finds the same 263 combinations in 953,017 calls.

    Space: O(n) besides the answer: current and the recursion are at most n deep.

    Edge cases and common mistakes

    • Comparing with i > 0 instead of i > start. That also skips a repeat that is the first number a call tries. Example 1 then returns [[1,2,5],[1,7],[2,6]], without [1,1,6].
    • No skip at all. Example 1 returns 6 combinations, with [1,2,5] and [1,7] twice each. For [1,1,7] and a target of 8 it returns [[1,7],[1,7]].
    • Forgetting to sort. The skip and the break both depend on the order. Without sorting, example 1 returns [].
    • Recursing with i instead of i + 1. A number could then be used again and again, which this problem forbids.
    • Using a repeated value more than once. That is allowed as long as the input has the copies: [2,5,2,1,2] with target 5 gives [[1,2,2],[5]], using two of the three 2s.
    • No solution. [2,3] with target 4 gives [], and a single candidate that doesn't reach the target, [1] with target 2, also gives [].
    • Equal candidates that complete the target. [3,3,3] with target 6 gives only [[3,3]].

    Testing

    • [10,1,2,7,6,1,5], target 8 gives [[1,1,6],[1,2,5],[1,7],[2,6]].
    • [2,5,2,1,2], target 5 gives [[1,2,2],[5]].
    • [2,3], target 4 gives [].
    • [1,1,7], target 8 gives [[1,7]].
    • [1], target 1 gives [[1]].
    • [1,1,1,1], target 2 gives [[1,1]].
    Keep going with C++500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free