Question:
You are given an integer array coins
 representing coins of different denominations and an integer amount
 representing a total amount of money.
Return the number of combinations that make up that amount. If that amount of money cannot be made up by any combination of the coins, return 0
.
You may assume that you have an infinite number of each kind of coin.
The answer is guaranteed to fit into a signed 32-bit integer.
Example 1:
Input: amount = 5, coins = [1,2,5] Output: 4 Explanation: there are four ways to make up the amount: 5=5 5=2+2+1 5=2+1+1+1 5=1+1+1+1+1
Example 2:
Input: amount = 3, coins = [2] Output: 0 Explanation: the amount of 3 cannot be made up just with coins of 2.
Solution:
Recursive and Memoized Solution:
class Solution {
public int change(int amount, int[] coins) {
int[][] dp = new int[coins.length][amount + 1];
for(int[] mem: dp){
Arrays.fill(mem, -1);
}
return changeUtil(amount, 0, coins, dp);
}
public int changeUtil(int amount, int idx, int[]coins, int[][] dp){
if(amount == 0){
return 1;
}
if(idx >= coins.length){
// If the amount is also 0, there's 1 way to make the change
// Otherwise, there's no way to make the change
return amount == 0 ? 1 : 0;
}
if(dp[idx][amount] != -1){
return dp[idx][amount];
}
// Number of ways by excluding the current coin
int noCallWays = changeUtil(amount, idx + 1, coins, dp);
// Number of ways by including the current coin one or more times
//(for example: coin of denomination 2 for amount 10, can be picked 5 times)
int yesCallWays = 0;
for(int time = 1; time <= amount/coins[idx]; time++){
int remainingAmount = amount - (coins[idx] * time);
yesCallWays += changeUtil(remainingAmount, idx + 1, coins, dp);
}
return dp[idx][amount] = noCallWays + yesCallWays;
}
}
Tabulation:
class Solution {
public int change(int amount, int[] coins) {
int[][] dp = new int[coins.length + 1][amount + 1];
//Base case
// Initialize dp[i][0] to 1 for all i, as there's one way to make 0 amount (using no coins)
for(int i = 0; i < dp.length; i++){
dp[i][0] = 1;
}
for(int i = 1; i <= coins.length; i++){
for(int j = 1; j <= amount; j++){
dp[i][j] = dp[i - 1][j]; //noCall
if(j >= coins[i - 1]){ //yesCall
dp[i][j] += dp[i][j - coins[i - 1]];
}
}
}
return dp[coins.length][amount];
}
}
// Solution with more meaningful names
class Solution {
public int change(int targetAmount, int[] coinDenominations) {
// Create a DP array where dp[i][j] represents the number of ways
// to make amount 'j' using the first 'i' coins.
int[][] waysToMakeAmount = new int[coinDenominations.length + 1][targetAmount + 1];
// Base case: There's one way to make amount 0, which is by using no coins.
for (int coinIndex = 0; coinIndex <= coinDenominations.length; coinIndex++) {
waysToMakeAmount[coinIndex][0] = 1;
}
// Fill the DP table
for (int coinIndex = 1; coinIndex <= coinDenominations.length; coinIndex++) {
for (int currentAmount = 1; currentAmount <= targetAmount; currentAmount++) {
// Option 1: Don't use the current coin (inherited from previous row)
waysToMakeAmount[coinIndex][currentAmount] = waysToMakeAmount[coinIndex - 1][currentAmount];
// Option 2: Use the current coin if it doesn't exceed the current amount
if (currentAmount >= coinDenominations[coinIndex - 1]) {
// Add the number of ways to make the remaining amount (currentAmount - coinValue)
waysToMakeAmount[coinIndex][currentAmount] += waysToMakeAmount[coinIndex][currentAmount - coinDenominations[coinIndex - 1]];
}
}
}
// Return the number of ways to make the targetAmount using all coins
return waysToMakeAmount[coinDenominations.length][targetAmount];
}
}
Space Optimized:
class Solution {
public int change(int amount, int[] coins) {
int[] prev = new int[amount + 1];
int[] curr = new int[amount + 1];
prev[0] = 1;
for(int i = 1; i <= amount; i++){
prev[i] = 0;
}
for(int coin: coins){
for(int i = 0; i <= amount; i++){
curr[i] = prev[i]; // no call
if(i >= coin){
// yesCall (prev + ways to make the reduced amt in prev array)
curr[i] = prev[i] + curr[i - coin];
//or curr[i] += curr[i - coin];
}
}
prev = curr.clone();
}
return prev[amount];
}
}