Start learning free

LESSON 366 OF 573 · BACKTRACKING

Combination Sum II in Go: Backtracking Without Reuse

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

Given an array 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 array.

 

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 13
    Ctrl + Enter to check

    GO · BACKTRACKING

    Combination Sum Ii in Go, explained

    Problem overview

    The function combinationSum2 receives a slice candidates, which may contain repeated numbers, and an integer target. It returns every distinct combination of candidates that adds up to target. Each candidate can be used at most once, so a value 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 they use. With candidates = [1, 1, 7] and target = 8, the first 1 with the 7 and the second 1 with the 7 are one combination, [1 7], listed once. The numbers of each combination are in ascending order, and the combinations are returned in lexicographic order: the first position where two combinations differ decides which one comes first. If nothing adds up to the target, the result is empty.

    For [10, 1, 2, 7, 6, 1, 5] and a target of 8 the answer is [[1 1 6] [1 2 5] [1 7] [2 6]]. Sorted, the candidates are [1 1 2 5 6 7 10]. The combination [1 1 6] uses both 1s, and [1 2 5] comes before [1 7] because 2 is smaller than 7 at the second position. For [2, 5, 2, 1, 2] and a target of 5 the answer is [[1 2 2] [5]]. For [2, 3] and a target of 4 it is empty, because the 2 can't be used twice.

    Approach: sort, then pick the next number in a loop

    The lesson uses backtracking. First sort candidates. A call to backtrack gets start, the first index it may still use, remaining, the amount the combination still needs, and current, the combination built so far. When remaining is 0, current adds up to the target, so the call saves a copy of it. Otherwise a loop tries each index i from start on as the next number: add candidates[i], recurse with i + 1 and remaining - candidates[i], then remove the number again.

    Three details make it correct and fast:

    • Recurse with i + 1, not i. When a number may be reused, the deeper call starts at the same index. Here every candidate is used at most once, so the next number comes from further right. Passing i would let the search use one 1 over and over, and on the first example it would return combinations such as [1 1 1 1 1 1 1 1].
    • Skip repeated values in the loop. Inside one call, each turn of the loop picks a different next number. A second copy of the same value would rebuild the same combinations. So the loop skips candidates[i] when i > start and it equals candidates[i-1]. At i == start the number is the first one this call tries, so it is taken, and that is how [1 1 6] gets its second 1.
    • Stop when a number is too big. The candidates are sorted, so once candidates[i] is bigger than remaining, every number after it is too big as well, and the loop can break.

    Step by step

    1. Sort candidates with sort.Ints. Equal numbers become neighbours, the combinations come out in ascending order, and the loop can stop early.
    2. Create result as an empty [][]int.
    3. Start the search with backtrack(candidates, 0, target, []int{}, &result): any index may be used, all of the target is still needed, the combination is empty, and the helper gets the address of result.
    4. Return result after the call.
    5. Write the helper backtrack with the parameters candidates, start, remaining, current and result *[][]int.
    6. Check for a finished combination: if remaining == 0.
    7. Save a copy of current with append([]int{}, current...) and return. Every candidate is at least 1, so adding more would pass the target.
    8. Loop with i from start to the end of the slice. Starting at start keeps each combination in index order, so no set of indices is built twice.
    9. Skip repeats: when i > start and candidates[i] == candidates[i-1], use continue.
    10. Stop at the first number that is too big: when candidates[i] > remaining, use break.
    11. Choose the number by appending it to current.
    12. Recurse with i+1 and remaining-candidates[i].
    13. Undo the choice with current = current[:len(current)-1].

    The solution in Go

    func combinationSum2(candidates []int, target int) [][]int {
    	sort.Ints(candidates)
    	result := [][]int{}
    	backtrack(candidates, 0, target, []int{}, &result)
    	return result
    }
    
    func backtrack(candidates []int, start int, remaining int, current []int, result *[][]int) {
    	if remaining == 0 {
    		*result = append(*result, append([]int{}, current...))
    		return
    	}
    	for i := start; i < len(candidates); i++ {
    		if i > start && candidates[i] == candidates[i-1] {
    			continue
    		}
    		if candidates[i] > remaining {
    			break
    		}
    		current = append(current, candidates[i])
    		backtrack(candidates, i+1, remaining-candidates[i], current, result)
    		current = current[:len(current)-1]
    	}
    }

    The Go details worth a closer look:

    • The copy matters. current shares its backing array with the slices of other calls and keeps changing. append([]int{}, current...) builds a new slice that no later call can overwrite.
    • A pointer to the result. append may return a slice with a new backing array, so the helper writes the new slice back through *result, where combinationSum2 can see it.
    • Sorting changes the caller's slice. sort.Ints works in place, so after the call a caller's [10, 1, 2, 7, 6, 1, 5] is [1 1 2 5 6 7 10].
    • Undoing with a slice expression. current[:len(current)-1] drops the last element without allocating. The next append reuses that slot, which is safe because the saved copy is separate.
    • Empty result. The result starts as [][]int{}, so when nothing matches the function returns an empty slice that prints as [], not nil.

    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. Sorting adds O(n log n). In practice the search is much smaller, because it only builds distinct combinations whose sum stays within the target.

    Space: O(n) beyond the answer: the combination being built and one call per number in it.

    Edge cases and common mistakes

    • No skip line. On the first example, the search without the repeat check returns [[1 1 6] [1 2 5] [1 7] [1 2 5] [1 7] [2 6]], where [1 2 5] and [1 7] appear twice.
    • Comparing with i > 0. Writing i > 0 instead of i > start also skips the second 1 of [1 1 6]. The first example then returns only [[1 2 5] [1 7] [2 6]], and [2, 5, 2, 1, 2] with a target of 5 returns [[5]], losing [1 2 2].
    • Forgetting to sort. The repeat check compares neighbours and the break assumes ascending order. If the first example is searched without sorting, the 10 at index 0 triggers the break at once and the search finds nothing.
    • Using a value twice when it appears once. [2, 3] with a target of 4 returns [].
    • Many equal values. [1, 1, 1, 1] with a target of 2 returns just [[1 1]], one combination, not one for every pair of 1s.
    • Single candidate. [3] with a target of 3 gives [[3]], and with a target of 4 gives [].

    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, 1, 1, 1], target 2 gives [[1 1]].
    Keep going with Go500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free