Problem overview
The function change receives a target amount and a vector of distinct coin values, coins. It returns how many different combinations of coins add up to exactly amount, or 0 if none do. Each coin value can be used any number of times. A combination counts how many coins of each value it uses, not their order, so 2 + 2 + 1 and 1 + 2 + 2 are the same combination.
For amount = 5 and coins = [1, 2, 5] the answer is 4: 5, 2 + 2 + 1, 2 + 1 + 1 + 1 and 1 + 1 + 1 + 1 + 1. For amount = 3 and coins = [2] the answer is 0, because coins of 2 only make even sums. For amount = 0 the answer is 1: the combination that uses no coins at all.
Approach: count the ways to make every smaller sum
Listing every combination is hopeless for large inputs, since the number of combinations grows very quickly. The lesson counts them instead. Let ways[a] be the number of combinations that make the sum a, for every a from 0 to amount. The answer is ways[amount].
The table starts with ways[0] = 1, the empty combination, and every other entry at 0. Then the coins are added one at a time. For a given coin, every combination for the sum a - coin, plus one more of this coin, is a combination for a. So ways[a] grows by ways[a - coin]. Going through the sums upward matters: ways[a - coin] already includes combinations that use this coin, so one coin value can be used again and again.
The order of the two loops is what makes this count combinations and not orders. While a coin is being added, the table only contains combinations built from the coins before it. Each combination is therefore assembled in one fixed order, all its coins of the first value, then of the second, and so on, and it is counted exactly once. With the sums in the outer loop and the coins inside, any coin could be the last one added to any sum, so 1 + 3 and 3 + 1 would both be counted.
Here is the table for amount = 5 and coins = [1, 2, 5], after each coin is added (index 0 is first):
start [1, 0, 0, 0, 0, 0]
coin 1 [1, 1, 1, 1, 1, 1]
coin 2 [1, 1, 2, 2, 3, 3]
coin 5 [1, 1, 2, 2, 3, 4]
With only 1-coins, every sum has one way. Adding 2-coins gives sum 4 three ways (1+1+1+1, 2+1+1, 2+2), and adding the 5-coin gives sum 5 one more way, for a total of 4.
Step by step
- Create
waysas a mutable vector ofamount as usize + 1zeros. The sizes and indexes in Rust areusize, soamountis converted, and the zero is written0i32so the counts have the same type as the return value. - Set
ways[0]to 1. The sum 0 has exactly one combination, the one with no coins, and every other combination grows from it. - Loop over the coins with
&coins, so the vector is borrowed, and the pattern&coin, which copies each value out. The coins are the outer loop. - Loop over the sums from
coinup toamount, inclusive. A sum smaller than the coin can't contain it. Going upward lets a smaller sum already include this coin. - Add
ways[a - coin as usize]toways[a]. This is the key line: every way to makea - coin, plus one coin, is a way to makea. - Return
ways[amount as usize]as the last expression, with no semicolon.
The solution in Rust
impl Solution {
fn change(amount: i32, coins: Vec<i32>) -> i32 {
let mut ways = vec![0i32; amount as usize + 1];
ways[0] = 1;
for &coin in &coins {
for a in coin as usize..=amount as usize {
ways[a] += ways[a - coin as usize];
}
}
ways[amount as usize]
}
}
The Rust details worth a closer look:
vec![0i32; n]builds a vector ofncopies of the value. Thei32suffix fixes the element type.- Casts and precedence.
asbinds tighter than-, soa - coin as usizemeansa - (coin as usize), which isusizeminususize. The loop only reaches sums wherea >= coin, so this subtraction never goes below zero. - The range
coin as usize..=amount as usize. The..=includes the end. Whencoinis larger thanamount, the range is empty and the loop body never runs. That also handlesamount = 0. for &coin in &coins. Iterating over&coinsyields&i32references, and the pattern&coincopies the number out socoinis a plaini32.- Overflow. The lessons build with
-O, wherei32overflow wraps silently, but the constraints promise that every count fits in 32 bits, so it doesn't happen. In a debug build the same overflow would panic.
Complexity
Time: O(n · amount), where n is the number of coins. Each coin makes one pass over at most amount + 1 sums, and each step is one addition.
Space: O(amount). ways holds amount + 1 counts, and nothing else grows with the input.
Edge cases and common mistakes
- Swapping the loops. With the sums outside and the coins inside,
amount = 4andcoins = [1, 2, 3]gives 7, not 4: it counts3 + 1and1 + 3separately. With the coins outside, the answer is 4. - Amount zero.
change(0, vec![7])returns 1, the empty combination. That is whyways[0]starts at 1 and not 0. - A coin larger than the amount.
change(3, vec![5])returns 0. The inner range is empty, sowayskeeps its initial values. - Starting the inner loop at zero.
a - coin as usizewould then be below zero for smalla. In Rust that is an overflow panic in a debug build and a wrapped, enormous index in a release build, which panics on the vector access. - Going downward in the inner loop. Counting sums from
amountdown tocoinlets each coin be used at most once, which solves a different problem. Foramount = 5andcoins = [1, 2, 5]it would not count2 + 2 + 1.
Testing
amount = 5,coins = [1, 2, 5]gives 4.amount = 3,coins = [2]gives 0.amount = 4,coins = [1, 2, 3]gives 4.amount = 0,coins = [7]gives 1.amount = 10,coins = [10]gives 1.amount = 3,coins = [5]gives 0.
