Search for unique combinations
“A package builder fills an exact capacity using reusable positive-size blocks. Users want every distinct combination, not every ordering of the same blocks. Produce inspectable answers without returning
[2,3,2]again when[2,2,3]already exists. What assumptions make the search terminate?”
Write this:
def combinations(candidates, target):
...
Constructed practice question. Prerequisite: backtracking. Backtracking extends a partial choice, explores its consequences, and restores the earlier state before trying a sibling choice. Canonical order avoids duplicate representations rather than removing duplicates after generating them.
| Contract | Required behavior |
|---|---|
| Input | Positive integer candidate sizes; nonnegative integer target |
| Output | All nondecreasing combinations summing to target, in lexical order |
| Reuse | Unlimited copies; repeated input candidates collapse |
| Boundaries | Target 0 gives [[]]; impossible target gives [] |
| Failure/scope | Zero/negative/noninteger candidates raise ValueError; no negative sizes |
Optional refresher · the underlying tool
Backtracking tries one choice, explores it, then undoes it before trying a sibling. Sort candidates and only choose indices at or after the current start to avoid generating permutations of the same combination:
path = [2, 2]
remaining = 3
path.append(3) # [2,2,3], remaining 0: emit a COPY
path.pop() # restore [2,2] for the next choice
With candidates [2,3,6,7,2] and target 7, expect [[2,2,3],[7]]. Positive sizes ensure remaining capacity decreases.
A design choice worth saying aloud
Treat path as mutable workspace owned by the current recursion branch. Append, recurse, and pop; append a copy to results at a solution, or every result can later change with the same list. Deduplicate candidate values up front and keep a nondecreasing start index so permutations do not masquerade as new combinations.
What the interviewer expects
The interviewer gives you the scenario and the contract above. Explain what a successful call returns, walk through one example below, and name what your state means before choosing a data structure.
Done means: All nondecreasing combinations summing to target, in lexical order.
Now predict each output before looking at the reference; invalid input should leave any existing state unchanged unless the contract says otherwise.
Test-case scenarios to settle before coding
01 · Representative
- Input / starting state
[2,3,6,7,2], target 7- Expected result
[[2,2,3],[7]]
What it is testing: Duplicate candidates collapse; reuse remains legal.
02 · Zero target
- Input / starting state
- candidates
[2,3], target0 - Expected result
[[]]
What it is testing: One empty combination reaches zero.
03 · Impossible
- Input / starting state
[4,6], target 5- Expected result
[]
What it is testing: No witness is not an exception.
04 · Lexical order
- Input / starting state
- candidates
[2,3,5], target8 - Expected result
[[2,2,2,2],[2,3,3],[3,5]]
What it is testing: Each combination is nondecreasing; output is lexically ordered.
05 · No mutation
- Input / starting state
- unsorted candidate input
- Expected result
- same input after return
What it is testing: Search works on owned normalized state.
06 · Invalid
- Input / starting state
- zero/negative/bool candidate or negative target
- Expected result
ValueError
What it is testing: Nonpositive choices could break termination.
For each case, show which branch or state change produces that result.
candidates=[2,3,6,7,2], target=7 returns [[2,2,3],[7]]. Target 5 with [4,6]
returns []. Zero-sized blocks must be rejected: repeatedly selecting zero would
never reduce remaining capacity. Ask whether each candidate can instead be used
once; that changes the state transition, not merely the output formatting.
Implement before opening the answer. Explain why forbidding smaller subsequent candidates loses no combination but removes permutations.
Solution, restoration, and follow-ups
A baseline tries every ordered sequence up to the target, sorts each successful sequence, and deduplicates the results. It repeatedly explores equivalent states: 2+2+3, 2+3+2, and 3+2+2 all consume the same capacity. Memoizing only remaining capacity also loses the information needed to enforce allowed candidate order.
Sort and deduplicate candidates. A state holds the remaining sum and the first candidate index allowed next. Choosing index i recurses with the same lower bound i, permitting reuse. Values exceeding the remainder prune all later values because the array is sorted. Emit a copy of the path only at remainder zero.
the path is nondecreasing, its sum plus remaining equals target, and every continuation uses an index at least the last choice. Every multiset has exactly one sorted representation, so it appears once. Positive values strictly decrease remaining, proving termination. The reference uses explicit frames and one mutable path, preserving recursive reasoning without a recursion-depth limit.
| Frame event | Path | Restoration needed |
|---|---|---|
| Choose 2,2,3 | [2,2,3] | Emit a copy |
| Return from zero remainder | [2,2] | Pop last choice |
| Exhaust remaining-3 frame | [2] | Restore parent before trying 3 |
Let m be supplied candidates, k distinct candidates, D=floor(target/minimum), N explored prefix states, and B total output entries. Time is O(m + k log k + N + B), auxiliary space O(k + D), and output space O(B). A loose worst-case search bound is O((k + 1)^D); reporting only O(target × k) would confuse enumeration with a counting DP. Empty candidates are handled separately without a minimum. Very large outputs remain expensive.
Follow-up 1 — each distinct size may be selected once. Predict the example:
[2,2,3] disappears and [7] remains. Move the lower bound to i+1 after selection.
If repeated input entries represent separate inventory units, do not deduplicate;
instead skip equal sibling choices while retaining their multiplicities.
Follow-up 2 — return the number only. Use DP with candidates outside and amounts
inside the loops to count unordered combinations. Reversing loop order counts
ordered sequences. Demonstrate the difference for target 3 with [1,2]: two
combinations versus three sequences.
Senior depth derives canonical state and restoration. Lead depth defines result limits, pagination, and cancellation before exposing exponential enumeration.
Reference: solution.py (download file, source below); tests compare independent count-vector enumeration, verify duplicates/zero/impossible inputs, and exercise a deep path.
"""Enumerate unique nondecreasing combinations, with unlimited positive values."""
def combinations(candidates, target):
if not isinstance(target, int) or target < 0:
raise ValueError("nonnegative integer target required")
unique = set()
for value in candidates:
if not isinstance(value, int) or value <= 0:
raise ValueError("positive integer candidates required")
unique.add(value)
values = sorted(unique)
result, path = [], []
# Each frame holds the next candidate index and remaining target.
stack = [[0, target]]
while stack:
index, remaining = stack[-1]
if remaining == 0:
result.append(path.copy())
stack.pop()
if stack:
path.pop()
elif index == len(values) or values[index] > remaining:
stack.pop()
if stack:
path.pop()
else:
stack[-1][0] += 1
path.append(values[index])
stack.append([index, remaining - values[index]])
return result
python -m unittest discover -s curriculum/01-code/02-data-structures-algorithms/problems/31-combination-search -p 'test_*.py'