skip to content

In count-the-ways DP with unlimited item types, why does moving the target loop outside change the count?

level: middleimportance: should knowfreq 55%

answer

  1. same cells, different question
  2. does purchase order count as different?
  3. an outer item loop fixes one order
  4. outer target loop lets any item be last
  5. sizes 1 and 2 to target 3: two or three?

basics

~20 s

An outer item loop fixes one item order, so each unordered selection is counted once — combinations. An outer target loop lets every item be the last one added at every target, so orderings count separately — permutations.

solid answer

~40 s

Both nestings fill the same table over targets, with the same base cell of 1 and the same additive step, and they compute different quantities. With items outside and targets inside, item `k` is only ever added after items `0..k-1` have finished, so every selection is counted in exactly one canonical order — that is a count of unordered multisets. With targets outside and items inside, `ways[t]` sums over every item that could have been added *last*, so the same multiset is recounted once per distinct arrangement — a count of ordered sequences. Take unlimited 1 GB and 2 GB add-on packs and a 3 GB target: item-outer gives 2 (`1+1+1`, `1+2`), target-outer gives 3, because `1+2` and `2+1` are different sequences. Decide which quantity the requirement wants before writing either loop.

code

pseudocode · 12 lines
pseudocode
// A: item loop outside -> counts unordered selections
waysA[0] = 1
for i in 0..n-1:
    for t in size[i]..T:
        waysA[t] = waysA[t] + waysA[t - size[i]]

// B: target loop outside -> counts ordered sequences
waysB[0] = 1
for t in 1..T:
    for i in 0..n-1:
        if size[i] <= t:
            waysB[t] = waysB[t] + waysB[t - size[i]]

go deeper

for a junior

Know that a ways-counting table starts with one way to reach a target of zero, and that swapping the two loops is not a free refactor. You are not expected to derive the theorem yet.

for a middle

Be able to hand-trace two item sizes to a small target and produce both numbers, the unordered count and the larger ordered one, then say which nesting produced which and why.

for a senior

Show that you pin the intended semantics before coding: ask whether orderings count, then add a test on a target where the two answers differ so a later loop swap fails loudly instead of quietly.

for a principal

Own the ambiguity itself. 'Number of ways' is under-specified in a requirement, and the cost of shipping the wrong quantity is a wrong number in a report nobody can eyeball. Make the specification name it.

## Two nestings, one table A plan builder offers add-on data packs in a few fixed sizes, each purchasable any number of times, and you want to know how many ways a customer can assemble exactly a target allowance. The table is an array `ways` indexed by target, with `ways[0] = 1` — there is exactly one way to reach zero, by selecting nothing — and every other cell starting at 0. The update is always `ways[t] = ways[t] + ways[t - size]`. What is *not* fixed is which loop is outside. Both nestings touch the same cells with the same arithmetic. They answer different questions, and the gap between the answers grows fast. ## Item loop outside: unordered selections Process pack sizes one at a time; for each, sweep targets upward and fold that pack into every cell it can reach. When pack `k` is being folded in, the table already reflects exactly the selections buildable from packs `0..k-1`, and after the sweep it reflects selections buildable from packs `0..k`, with any number of copies of pack `k`. The key property is that a selection is only ever built in one canonical order: non-decreasing pack index. There is no moment at which the algorithm can add a 1 GB pack after it has finished with 2 GB packs, because the 1 GB stage is over. So every multiset is counted exactly once. This is the count of **combinations**. ## Target loop outside: ordered sequences Now sweep targets from 1 upward and, at each target, sum over every pack that could have been the last one bought. `ways[t] = sum over sizes s of ways[t - s]`. This is a completely legitimate recurrence — but read what it says. It classifies each way to reach `t` by its final purchase. Two sequences that use the same multiset but end differently are in different classes and are both counted. This is the count of **ordered sequences**, that is, of purchase histories. ## The hand trace that settles it Packs of 1 GB and 2 GB, target 3 GB. Item-outer. After folding in the 1 GB pack: `ways = [1, 1, 1, 1]` — one way to reach each of 0, 1, 2, 3 using only 1 GB packs. Now fold in the 2 GB pack, sweeping targets 2 and 3: `ways[2] += ways[0]` gives 2, `ways[3] += ways[1]` gives 2. Final answer 2: `{1,1,1}` and `{1,2}`. Target-outer. `ways[0] = 1`. `ways[1] = ways[0] = 1`. `ways[2] = ways[1] + ways[0] = 2`. `ways[3] = ways[2] + ways[1] = 3`. Final answer 3: the sequences `1,1,1`, `1,2` and `2,1`. Two lines of code apart, and 2 versus 3. Extend the target and the ordered count grows exponentially while the unordered count grows polynomially in the number of pack types — this is not a rounding difference you can patch up afterwards. ## The mistake to name The misconception to shoot down explicitly is "swapping the two loops is a harmless refactor — the same cells get filled either way". The cells *are* the same. The dependency structure is not. Someone tidying a nested loop for readability, or reordering to hoist a bounds check, can silently change a report's meaning, and nothing crashes. A related error is believing the two counts are related by a simple correction factor — divide by a factorial, say. They are not, because different multisets have different numbers of arrangements depending on how many repeats they contain, so no single factor converts one count into the other. ## When nesting does not matter If the aggregator is a maximum, a minimum, or a boolean OR, the nesting is irrelevant: those aggregators do not distinguish between two paths to the same state, so reachability and optimal totals come out the same either way. Nesting matters exactly when the aggregator **counts distinct constructions**, because then the question of what makes two constructions distinct is being answered implicitly by the loop order. ## What to do in the interview and in the code Say which quantity each nesting computes before you write it, then ask which one is wanted. "Number of ways" is genuinely ambiguous in a requirement, and the ordered reading is sometimes the intended one — a count of purchase histories or of step sequences is a real question. Then protect the choice: a single unit test on a target where the two counts differ (three is enough, with sizes 1 and 2) will fail loudly the day someone swaps the loops. ## Cost Both nestings are O(n * target) time and O(target) space for `n` item types. The counts themselves grow quickly, so the running total is the practical constraint long before the loop is — an ordered count over even a modest target will exceed a fixed-width integer, which is its own review checkpoint.

  • A requirement says 'count the ways to reach the target'. Which nesting do you write?
    Neither, until the ambiguity is closed. In most interview framings the unordered count is intended, so item-outer is the default guess — but say what each nesting counts, name the small case where they differ, and ask. Counting purchase histories or step sequences is a legitimate requirement too, and shipping the wrong one produces a wrong number that nobody can spot by eye.
  • Does the nesting matter if you only want a yes/no answer on whether the target is reachable?
    No. With a boolean OR aggregator both nestings mark exactly the same set of reachable targets, because reachability does not distinguish two ways of arriving. The same holds for maximizing or minimizing aggregators. Nesting matters precisely when the aggregator counts distinct constructions, since the loop order then defines what counts as distinct.
  • Can you convert the ordered count into the unordered one by dividing by a factorial?
    No. Each multiset has a different number of arrangements depending on how many repeats it contains, so no single divisor relates the two totals. The only reliable way to get the unordered count is to compute it with the item-outer nesting, which builds each selection in one canonical order and never counts it twice.

Counting handshakes versus counting introductions: the same pairs of people, but one of the two quantities cares who moved first.

saying these in an interview costs you the question

  • Says swapping the two loops is a harmless refactor
  • Claims the item-outer nesting counts orderings
  • Thinks the difference is only cache behaviour
  • Divides the ordered count by a factorial to fix it
  • Starts the zero-target cell at 0 instead of 1

context