Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
Backtracking  ›  Lesson

Prune Early: Combinations That Hit a Total

Combination Sum 15 minWhen backtracking toward a target total, sort the values, move forward from a start index so each combination is built once, and cut off a branch as soon as the next value is larger than what remains.

Prune Early: Combinations That Hit a Total

A post office sells stamps worth 2, 3 and 5 cents, as many of each as you like. A parcel needs exactly 8 cents of postage. Which stamp combinations work? Listing every possible pile of stamps and then checking the totals would explore a huge tree full of hopeless branches. Good backtracking cuts those branches off the moment they are doomed.

The rule

Stop a branch the moment it can no longer succeed.

Cutting off a doomed branch is called pruning. Here, a branch is doomed as soon as the next stamp is bigger than the postage still needed. Sort the values first, and one such stamp proves every later stamp is too big as well, so the loop can break, not just skip:

def ways_to_total(values, target):
    values = sorted(values)
    result, path = [], []
    def explore(start, remaining):
        if remaining == 0:
            result.append(path[:])
            return
        for i in range(start, len(values)):
            value = values[i]
            if value > remaining:
                break                        # sorted: every later value is bigger too
            path.append(value)
            explore(i, remaining - value)    # i, not i + 1: this stamp may be reused
            path.pop()
    explore(0, target)
    return result

Two other details carry the design:

  • start means each branch only uses stamps at or after the current one. So stamps are always added in sorted order, and [2, 3, 3] can never reappear as [3, 2, 3] or [3, 3, 2].
  • explore(i, ...) lets the same stamp be picked again. Passing i + 1 instead would allow each value at most once.

Pruning never changes which answers are found, only how much of the tree gets visited. For postage of 8 cents the saving is small, but as the target grows, dead branches vastly outnumber live ones, and pruning is what keeps backtracking usable.

The shortcut

For every backtracking problem, ask three questions before coding: What is one choice? (the next stamp) When is a path complete? (nothing remaining) When is a path hopeless? (the next stamp is too big). The third question is where the speed comes from.

Stamps [2, 3, 5], postage 8Step 1 of 5
  1. Keep taking 2s

    2, 2, 2, 2 brings the remainder to 0: record [2, 2, 2, 2]. Back at a remainder of 2, the 3 is too big, so break.

The trap is writing continue where break belongs, or break on unsorted values. continue is correct but wastes time; break without sorting silently drops real answers.

Drills

Answer fast — trust the rule.

quick drill

How many combinations does ways_to_total([2, 3, 5], 10) return?

quick drill

With explore(i + 1, remaining - value) instead, what does ways_to_total([2, 3, 5], 8) return?

quick drill

If every call looped from 0 instead of from start, what would go wrong?

quick drill

What does ways_to_total([2, 4], 7) return?

Remember

  • Stop a branch the moment it can no longer succeed.
  • Shortcut: one choice, complete when, hopeless when; the last one is the speed.
  • Trap: break on unsorted values, or looping from 0 and getting repeats.

Practice — level up

0/1 solved

Write your answer, then compare it with a rubric and a model answer.

ExplainWhy break needs sorting

Suppose someone removes the sorted call and passes the stamps as [5, 2, 3] with postage 8. Explain which combination ways_to_total would now miss and exactly why, and then explain why break is safe once the values are sorted.