LeetCode #40 Medium

Combination Sum II

Given candidates (which may contain duplicates) and a target, return every unique combination summing to target. Each number may be used at most once.

backtrackingrecursionarray
Open on LeetCode ↗
02

Intuition

💡

Sort first so duplicates sit together. Then each recursive call moves to i + 1 (no reuse), and within one level you skip a candidate equal to the previous one to avoid duplicate combinations.

03

Approach

1

Two new twists over Combination Sum I

This variant changes two rules: each number may be used at most once, and the input may itself contain duplicates. The first is easy — recurse with i + 1 instead of i. The second is the subtle part: with duplicate candidates, naive backtracking would emit the same combination more than once, so we need a principled way to skip duplicates without missing valid answers.

2

Sort, so duplicates sit together

Sort the candidates first. This does double duty: it groups equal values next to each other (making duplicates detectable), and it lets us prune early — once a candidate exceeds remain, every later candidate is larger too, so we can break the loop entirely. Within one recursion level, we then skip a candidate that equals the previous one (candidates[i] == candidates[i-1] when i > start).

3

Why the skip rule is exactly right

The condition i > start is doing precise work: it skips a duplicate only when it would start a sibling branch at the same depth (which would regenerate an identical combination), but still allows the duplicate to be used deeper in the tree (recursing with i + 1), so combinations like [1,1,6] that legitimately use two 1s are preserved. Record a copy of the path at remain == 0, prune/break on overflow, and pop to backtrack. Worst case O(2^N).

04

Solution & live demo

python
1class Solution:
2 def combinationSum2(self, candidates, target):
3 candidates.sort()
4 res = []
5 def backtrack(start, remain, path):
6 if remain == 0:
7 res.append(path[:])
8 return
9 for i in range(start, len(candidates)):
10 if i > start and candidates[i] == candidates[i - 1]:
11 continue
12 c = candidates[i]
13 if c > remain:
14 break
15 path.append(c)
16 backtrack(i + 1, remain - c, path)
17 path.pop()
18 backtrack(0, target, [])
19 return res
05

Edge cases

Duplicate candidates, e.g. [1,1,2]

The i > start and c == prev skip ensures the two 1s don't create identical combinations.

No combination sums to target

Every branch is pruned or exhausted, leaving an empty result.

Sorted-order pruning

Because the list is sorted, once c > remain the remaining candidates are larger too, so the loop can stop early.

06

Complexity

Time
O(2^N)
Space
O(N)
Subset exploration with pruning; recursion depth up to N.