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];
}
}