Skip to content
← All posts

Combination Sum III: Backtracking with Fixed Size

7 min read
leetcodeproblemmediumbacktracking

Find all valid combinations of k numbers that sum up to n such that only digits 1 through 9 are used, and each digit is used at most once. Return a list of all possible valid combinations.

This is LeetCode 216: Combination Sum III. Unlike Combination Sum where you pick from an arbitrary list with unlimited reuse, and Combination Sum II where candidates can repeat, here the candidate pool is fixed (digits 1 to 9), each digit is used at most once, and the combination must contain exactly k numbers. The fixed-size constraint adds an extra layer of pruning that makes this problem surprisingly clean.

+1+2+3+4+2+3+4+5+3+4+4+3+4+4+5+6+4+5[] rem=7[1] rem=6[2] rem=5[3] rem=45+6>3[1,2] rem=4[1,3] rem=3[1,4] rem=2[1,5] rem=1[2,3] rem=2[2,4] rem=1[3,4] len=2[1,2,3] r=1[1,2,4] r=04>35>26>14>25>1startexploringresult (len=k, rem=0)pruned (exceeds remaining or wrong sum)
Backtracking tree for k=3, n=7. We pick digits 1 through 9 in increasing order, each used at most once. The green node [1,2,4] is the only valid combination (1+2+4=7 with exactly 3 numbers). Red nodes are pruned when the next digit exceeds the remaining sum.

Why this problem matters

Combination Sum III is one of the best problems for learning how constraints simplify backtracking. You already know the choose-explore-unchoose template from Combinations and the sum-target pattern from Combination Sum. This problem combines both: you need exactly k numbers, and they must sum to n. The two constraints work together to prune the search tree aggressively.

The problem also teaches you to think about pruning from multiple angles. You can prune when a digit exceeds the remaining sum. You can also prune implicitly through the fixed-size requirement, because once you have picked k numbers, you stop immediately and check whether the sum matches. Both of these keep the recursion tree small even though the digit pool goes up to 9.

The key insight

Pick digits in increasing order (1, 2, 3, ..., 9) to avoid duplicates. At each recursion level, try the next digit starting from one above the last digit you picked. This guarantees every combination is generated in sorted order, so you never produce [2,1,4] and [1,2,4] as separate results.

The algorithm has two base cases. When the combination reaches length k, check if the remaining sum is 0. If yes, record the combination. If not, return immediately. There is no point exploring further because adding more digits would exceed k.

For pruning, if the current digit i exceeds the remaining sum, break. Since digits are tried in order, every digit after i is even larger, so none of them can work either. This single check eliminates large sections of the tree.

The backtracking solution

def combinationSum3(k: int, n: int) -> list[list[int]]:
    result = []

    def backtrack(start, combo, remaining):
        if len(combo) == k:
            if remaining == 0:
                result.append(combo[:])
            return
        for i in range(start, 10):
            if i > remaining:
                break
            combo.append(i)
            backtrack(i + 1, combo, remaining - i)
            combo.pop()

    backtrack(1, [], n)
    return result

Let's break this down.

The outer function initializes the result list and kicks off the recursion starting from digit 1 with an empty combination and the full target n as the remaining sum.

Inside backtrack, the first check is the length. When len(combo) == k, we have picked enough numbers. If remaining == 0, the combination sums to n exactly, so we record a copy. Either way, we return. This is the fixed-size constraint in action: once you reach k numbers, you stop.

The for loop tries digits from start up to 9. The guard if i > remaining triggers a break. Since we iterate digits in ascending order, if digit i already exceeds what we need, no larger digit can help.

After the guard, we append digit i (choose), recurse with i + 1 and reduced remaining (explore), and pop it off (unchoose). Passing i + 1 ensures each digit is used at most once, and picking in order avoids duplicate combinations.

The break on i > remaining is safe because digits increase. If you need a remaining sum of 4 and you are looking at digit 5, then 6, 7, 8, and 9 are all too large as well.

Visual walkthrough

Let's trace through the algorithm with k=3 and n=7. We need exactly 3 digits from 1 to 9 that add up to 7. Watch how the pruning eliminates most branches quickly.

Step 1: Start with an empty combination. k=3, n=7.

combo: []
remaining: 7
next digits from: 1

We need exactly 3 numbers from 1 to 9 that sum to 7. Try digit 1 first.

Step 2: Pick 1. Remaining = 7 - 1 = 6. Need 2 more numbers.

combo: [1]
remaining: 6
next digits from: 2

Length is 1, need k=3 total. Try digit 2 next.

Step 3: Pick 2. Remaining = 6 - 2 = 4. Need 1 more number.

combo: [1, 2]
remaining: 4
next digits from: 3

Length is 2. One more number needed. Try digit 3.

Step 4: Pick 3. Length = 3 = k, but remaining = 4 - 3 = 1, not 0. No match.

combo: [1, 2, 3]
remaining: 1
next digits from: 4

We have 3 numbers but 1+2+3=6, not 7. Backtrack: pop 3, try 4.

Step 5: Pick 4. Length = 3 = k, remaining = 4 - 4 = 0. Found a result!

combo: [1, 2, 4]
remaining: 0
next digits from: 5
results so far:[1,2,4]

1+2+4=7 with exactly 3 numbers. Record [1,2,4]. Backtrack: pop 4, try 5.

Step 6: Try 5 from [1,2]. But 5 > remaining (4). Prune.

combo: [1, 2]
remaining: 4
next digits from: 5
results so far:[1,2,4]

Since digits are tried in order and 5 already exceeds remaining=4, all larger digits will too. Break out of loop. Backtrack to [1].

Step 7: Back at [1]. Try 3. [1,3] remaining=3. Then try 4: 4 > 3. Prune.

combo: [1, 3]
remaining: 3
next digits from: 4
results so far:[1,2,4]

Need 1 more number but smallest available (4) exceeds remaining (3). Dead end. Backtrack.

Step 8: [1,4] rem=2, [1,5] rem=1. All next digits too large. Dead ends.

combo: [1, 4] then [1, 5]
remaining: 2
next digits from: 5 then 6
results so far:[1,2,4]

For [1,4]: 5>2, pruned. For [1,5]: 6>1, pruned. All branches from [1] are exhausted. Backtrack to root.

Step 9: Try [2]. [2,3] rem=2, 4>2 pruned. [2,4] rem=1, 5>1 pruned.

combo: [2, 3] then [2, 4]
remaining: 2
next digits from: 4 then 5
results so far:[1,2,4]

Starting from digit 2. Both two-digit paths from [2] lead to pruned branches because the next available digit exceeds the remaining sum.

Step 10: Try [3], [4], etc. All pruned. Done.

combo: [3] then [4]
remaining: 4
next digits from: 4 then 5
results so far:[1,2,4]

[3] needs 2 more summing to 4, smallest pair is 4+5=9 which exceeds 4. [4] needs 2 more summing to 3, smallest pair is 5+6=11. All pruned. Final result: [[1,2,4]].

The key observation is how fast the tree collapses. After finding [1,2,4] in Step 5, every other branch hits the i > remaining guard and gets pruned. The fixed-size constraint (len(combo) == k) means we never explore beyond 3 levels deep, and the sum constraint cuts off branches where digits are too large. Together, these two constraints make the actual work much smaller than the theoretical search space of all subsets of {1,...,9}.

How this differs from other Combination Sum problems

AspectCombination SumCombination Sum IICombination Sum III
Candidate poolGiven list (distinct)Given list (may repeat)Fixed: digits 1 to 9
ReuseUnlimitedEach element onceEach digit once
Size constraintNoneNoneExactly k numbers
Duplicate handlingNot neededSame-level skipNot needed (distinct digits)
Extra pruningi > remainingi > remainingi > remaining AND len == k

The fixed candidate pool and size constraint make Combination Sum III the cleanest of the three. No sorting needed (digits are already in order). No duplicate handling needed (all digits are unique). The only additions are the k check and the fact that the loop goes from start to 9 instead of iterating over a candidates array.

Complexity analysis

AspectComplexity
TimeO(C(9, k))
SpaceO(k)

Time is O(C(9, k)), which is the number of ways to choose k digits from 9. For k=3 this is C(9,3)=84. In practice, the i > remaining pruning means we visit far fewer nodes than 84. The upper bound is small because the digit pool is capped at 9.

Space is O(k) for the recursion stack and the current combination. The maximum recursion depth is k, and the combination holds at most k elements. Output space is separate and proportional to the number of valid combinations.

The building blocks

This problem is built from three core reusable pieces that CodeBricks drills independently.

Backtracking template (choose-explore-unchoose)

The same skeleton that powers Combination Sum, Combinations, and Combination Sum II:

def backtrack(state):
    if is_solution(state):
        record(state)
        return

    for choice in candidates(state):
        make_choice(choice)
        backtrack(state)
        undo_choice(choice)

In Combination Sum III, make_choice is combo.append(i) and undo_choice is combo.pop(). The recursion uses i + 1 to enforce the "each digit at most once" rule.

Pruning by remaining sum

When the next candidate exceeds what you still need, skip it and everything after it:

if i > remaining:
    break

This pattern appears in every combination sum variant. It relies on candidates being in sorted order (which digits 1 to 9 naturally are).

Fixed-size constraint

When the combination reaches the required length, check immediately and return:

if len(combo) == k:
    if remaining == 0:
        result.append(combo[:])
    return

This is the key difference from Combination Sum I and II. The return after the length check prevents the recursion from going deeper than k levels, which dramatically reduces the search space.

Edge cases

Before submitting, check these:

  • k=1: The answer is just [[n]] if n is between 1 and 9, and [] otherwise. Single digit must equal the target.
  • n too large for k digits: The maximum sum of k digits is 9 + 8 + 7 + ... (the k largest). For k=2, max is 9+8=17. If n=18 and k=2, return [].
  • n too small for k digits: The minimum sum of k digits is 1 + 2 + 3 + ... (the k smallest). For k=3, min is 1+2+3=6. If n=5 and k=3, return [].
  • k=9: You must use all digits 1 through 9. The only valid n is 1+2+...+9=45. Any other value returns [].
  • Multiple valid combinations: For k=3 and n=9, valid results are [[1,2,6], [1,3,5], [2,3,4]]. The algorithm finds all of them by exploring all branches.

From understanding to recall

You see how the backtracking tree works. The i + 1 prevents digit reuse, the i > remaining prunes large digits, and the len(combo) == k check stops recursion at the right depth. The logic is clean and the code is short.

But can you write it from scratch in under two minutes? That is the interview challenge. The most common mistakes under pressure are forgetting the break when i > remaining, using i instead of i + 1 in the recursive call, or returning without checking remaining == 0 in the base case. These are not conceptual problems. They are recall problems.

Spaced repetition fixes recall. You practice writing the backtracking template with the fixed-size constraint and the remaining-sum pruning from memory at increasing intervals. After a few rounds, the pattern is automatic. You see "find k numbers summing to n" and the code flows out without hesitation.

The backtracking template, remaining-sum pruning, and fixed-size constraint are three of roughly 60 reusable building blocks that cover hundreds of LeetCode problems. Learning them individually and drilling them with spaced repetition is far more effective than grinding random problems and hoping the patterns stick.

Related posts

  • Combination Sum - Unlimited reuse from a list of distinct candidates. Compare the structure to see how the fixed-size constraint simplifies things here.
  • Combination Sum II - Each candidate used once, but candidates can repeat. Requires sorting and same-level duplicate skipping, which Combination Sum III avoids entirely.
  • Combinations - Choose k numbers from 1 to n without a sum constraint. The closest structural sibling to this problem.

CodeBricks breaks Combination Sum III into its backtracking template, remaining-sum pruning, and fixed-size constraint building blocks, then drills them independently with spaced repetition. You type the skeleton and the guards from scratch until they are automatic. When a constrained backtracking problem shows up in your interview, you do not think about it. You just write it.