You are given an integer array coins representing coins of different denominations (e.g. 1 dollar, 5 dollars, etc) and an integer amount representing a target amount of money.
Return the number of distinct combinations that total up to amount. If it's impossible to make up the amount, return 0.
You may assume that you have an unlimited number of each coin and that each value in coins is unique.
Example 1:
Input: amount = 4, coins = [1,2,3]
Output: 4Explanation:
Example 2:
Input: amount = 7, coins = [2,4]
Output: 0Constraints:
1 <= coins.length <= 1001 <= coins[i] <= 50000 <= amount <= 5000
You should aim for a solution as good or better than O(n * a) time and O(n * a) space, where n is the number of coins and a is the given amount.
As we need to find the total number of combinations, think in terms of recursion and visualize it as a decision tree where multiple coin choices are available at each recursion step. Can you determine a way to allow picking the same coin multiple times? Maybe you should consider the decisions made at each recursion step.
The given coins are unique. We recursively iterate through the coins array using index i, tracking the collected amount along the current path. At each step, we can either skip the current coin or pick it, ensuring the total does not exceed the target. To allow picking the same coin multiple times, we recurse with the same index but an updated amount, generating different combinations.
If we reach the target amount, we return 1. The recursion stops if the index goes out of bounds. We count all possible ways and return the total. This approach is exponential. Can you think of a way to optimize it? Maybe you should consider an approach to avoid redundant computations.
We can use memoization to cache the results of recursive calls and avoid redundant computations. A hash map or a 2D array can be used to store these results.
This problem asks us to find the number of different ways to make up a given amount using unlimited coins of given denominations.
At every step, we make a choice for the current coin:
Recursion is a natural fit here because each choice leads to a smaller subproblem.
The recursive function represents:
“How many ways can we form amount a using coins starting from index i?”
By sorting the coins and always moving forward in the list, we avoid counting the same combination in different orders.
dfs(i, a):i is the current coin indexa is the remaining amounta becomes 0:1 because a valid combination is formedi goes out of bounds):0 because no combination can be formedres to 0a >= coins[i]):resres as the number of ways for the current state0 with the full amountWhere is the number of coins, is the given amount and is the minimum value among all the coins.
This problem is about counting how many different combinations of coins can make up a given amount, where each coin can be used any number of times.
The pure recursive solution works, but it recomputes the same subproblems again and again. To optimize this, we use top-down dynamic programming (memoization).
Each state is uniquely defined by:
iaThe function answers the question:
“How many ways can we form amount a using coins starting from index i?”
By storing results for each state, we avoid repeated calculations and greatly improve efficiency.
memo where:memo[i][a] stores the number of ways to form amount a using coins from index i onwarddfs(i, a):i is the current coin indexa is the remaining amounta becomes 0:1 since a valid combination is formedi is out of bounds):0 because no combination can be formedmemo:res to 0a >= coins[i]):resres in memo[i][a]0 with the full amountclass Solution:
def change(self, amount: int, coins: List[int]) -> int:
coins.sort()
memo = [[-1] * (amount + 1) for _ in range(len(coins) + 1)]
def dfs(i, a):
if a == 0:
return 1
if i >= len(coins):
return 0
if memo[i][a] != -1:
return memo[i][a]
res = 0
if a >= coins[i]:
res = dfs(i + 1, a)
res += dfs(i, a - coins[i])
memo[i][a] = res
return res
return dfs(0, amount)Where is the number of coins and is the given amount.
This problem asks for the number of different ways to make up a given amount using unlimited coins, where order does not matter.
Instead of using recursion, we can solve this using bottom-up dynamic programming, where we build the answer step by step using a table.
The key idea is to define a state that represents:
By filling the DP table from the base cases upward, we ensure that all required subproblems are already solved when needed.
n be the number of coins.dp of size (n + 1) x (amount + 1):dp[i][a] represents the number of ways to form amount a using coins from index i onwardi, set dp[i][0] = 1 since there is exactly one way to make amount 0 (choose no coins)i and for each amount a from 0 to amount:a >= coins[i]):dp[i + 1][a]dp[i][a - coins[i]]dp[i][a]dp[0][amount]dp[0][amount]class Solution:
def change(self, amount: int, coins: List[int]) -> int:
n = len(coins)
coins.sort()
dp = [[0] * (amount + 1) for _ in range(n + 1)]
for i in range(n + 1):
dp[i][0] = 1
for i in range(n - 1, -1, -1):
for a in range(amount + 1):
if a >= coins[i]:
dp[i][a] = dp[i + 1][a]
dp[i][a] += dp[i][a - coins[i]]
return dp[0][amount]Where is the number of coins and is the given amount.
This problem asks for the number of different combinations of coins that can make up a given amount, where each coin can be used unlimited times and the order of coins does not matter.
In the bottom-up dynamic programming approach, we used a 2D table to store results for each coin index and amount. However, each row only depends on:
Because of this, we can optimize the space and store only one 1D array at a time, updating it carefully to preserve correctness.
dp of size amount + 1:dp[a] represents the number of ways to form amount a using coins processed so fardp[0] = 1 since there is exactly one way to form amount 0nextDP to store updated resultsnextDP[0] = 1 as the base casea from 1 to amount:dp[a] (skipping the current coin)a - coins[i] >= 0):nextDP[a - coins[i]] (using the current coin again)dp with nextDP after processing the current coindp[amount] contains the total number of combinationsdp[amount]class Solution:
def change(self, amount: int, coins: List[int]) -> int:
dp = [0] * (amount + 1)
dp[0] = 1
for i in range(len(coins) - 1, -1, -1):
nextDP = [0] * (amount + 1)
nextDP[0] = 1
for a in range(1, amount + 1):
nextDP[a] = dp[a]
if a - coins[i] >= 0:
nextDP[a] += nextDP[a - coins[i]]
dp = nextDP
return dp[amount]Where is the number of coins and is the given amount.
We need to count how many different combinations of coins can make up a given amount, where:
From earlier dynamic programming approaches, we observe that for each coin, the number of ways to form an amount only depends on:
Because of this, we can use a single 1D DP array and update it in place, achieving the most space-efficient solution.
The DP array always represents the number of ways to form each amount using the coins processed so far.
dp of size amount + 1:dp[a] represents the number of ways to form amount adp[0] = 1 since there is exactly one way to form amount 01 to amount:dp[a - coin] to dp[a]dp[amount] holds the total number of combinationsdp[amount]Where is the number of coins and is the given amount.
Iterating over amounts in the outer loop and coins in the inner loop counts permutations (order matters), not combinations. This causes [1,2] and [2,1] to be counted as different ways.
# Wrong: Counts permutations
for a in range(1, amount + 1):
for coin in coins:
dp[a] += dp[a - coin]
# Correct: Counts combinations (coins in outer loop)
for coin in coins:
for a in range(coin, amount + 1):
dp[a] += dp[a - coin]Not initializing dp[0] = 1 means there is no way to build any amount. The base case represents the one valid way to make amount 0: use no coins.
When checking dp[a - coins[i]], failing to verify a >= coins[i] first causes negative index access or runtime errors.
# Wrong: Can access negative index
dp[a] += dp[a - coins[i]]
# Correct: Check before accessing
if a >= coins[i]:
dp[a] += dp[a - coins[i]]