class Solution:
def change(self, amount: int, coins: List[int]) -> int:
dp = [0] * (amount + 1)
dp[0] = 1
for coin in coins:
for x in range(coin, amount + 1):
dp[x] += dp[x - coin]
return dp[amount]
class Solution {
public:
int change(int amount, vector<int>& coins) {
vector<int> dp(amount + 1, 0);
dp[0] = 1;
for (int coin : coins) {
for (int x = coin; x <= amount; ++x) {
dp[x] += dp[x - coin];
}
}
return dp[amount];
}
};
class Solution {
public int change(int amount, int[] coins) {
int[] dp = new int[amount + 1];
dp[0] = 1;
for (int coin : coins) {
for (int x = coin; x <= amount; x++) {
dp[x] += dp[x - coin];
}
}
return dp[amount];
}
}
var change = function(amount, coins) {
let dp = new Array(amount + 1).fill(0);
dp[0] = 1;
for (let coin of coins) {
for (let x = coin; x <= amount; x++) {
dp[x] += dp[x - coin];
}
}
return dp[amount];
};
You are given an integer amount
and an array coins
representing different coin denominations. Your task is to determine the number of unique ways to make up the value amount
using any number of coins from coins
. Each coin can be used an unlimited number of times, but the order of coins does not matter (combinations, not permutations).
amount
.amount
, return 0
.
At first glance, this problem resembles the classic "coin change" problem, but instead of finding the minimum number of coins, we are counting all possible combinations that sum up to the target amount
. A brute-force approach would be to try every possible way to pick coins and count those that sum up to the target. However, this quickly becomes infeasible as the amount
and number of coins increase, due to the exponential number of possibilities.
To optimize, we recognize that this is a variation of the "Unbounded Knapsack" problem, where each coin can be used unlimited times. We need a way to systematically count combinations without repeating work. Dynamic Programming (DP) is a natural fit, as it allows us to build up solutions to smaller subproblems and avoid redundant calculations. The key insight is to count combinations (not permutations), so the order in which coins are chosen does not matter.
We use a 1-dimensional dynamic programming array dp
of size amount + 1
, where dp[x]
represents the number of ways to make up amount x
using the available coins.
dp[0] = 1
. There is one way to make amount 0: use no coins.
coin
up to amount
.
x
, update dp[x]
by adding dp[x - coin]
. This means: the number of ways to make x
increases by the number of ways to make x - coin
(by adding this coin).dp[amount]
contains the total number of combinations.
This approach avoids overcounting by ensuring that for each coin, we only consider combinations that include it or not, and always process coins in a fixed order.
Suppose amount = 5
and coins = [1, 2, 5]
.
dp = [1, 0, 0, 0, 0, 0]
(only dp[0]=1
)
x
from 1 to 5: dp[x] += dp[x - 1]
dp = [1, 1, 1, 1, 1, 1]
x=2
: dp[2] += dp[0] → 1+1=2
x=3
: dp[3] += dp[1] → 1+1=2
x=4
: dp[4] += dp[2] → 1+2=3
x=5
: dp[5] += dp[3] → 1+2=3
dp = [1, 1, 2, 2, 3, 3]
x=5
: dp[5] += dp[0] → 3+1=4
dp = [1, 1, 2, 2, 3, 4]
dp[5] = 4
The four combinations are: [1,1,1,1,1], [1,1,1,2], [1,2,2], [5].
n
coins and target amount
, the complexity is O(amount^n
).
amount × n
), where n
is the number of coin types. We process each coin for each amount.amount
) for the dp
array.The optimized approach is efficient and scales well for reasonable input sizes.
The Coin Change II problem is a classic example of dynamic programming, where we count the number of ways to reach a target sum using unlimited coins of given denominations. By carefully structuring our DP array and iterating coins in order, we efficiently compute the answer while avoiding overcounting. The 1D DP solution is both elegant and practical, turning an exponential brute-force problem into a linear one in terms of amount
and number of coins.