💡 Memoization caches results of subproblems to avoid repeated work, drastically improving efficiency over brute force.
Intuition
Use recursion but store results for each amount so that repeated calls return cached answers instantly.
Algorithm
- Initialize a memo dictionary to store minimum coins for each amount.
- If amount is 0, return 0.
- If amount is negative, return infinity.
- If amount is in memo, return stored result.
- For each coin, recursively compute minimum coins for amount - coin and update minimum.
- Store and return the minimum coins for current amount.
💡 Memoization reduces exponential calls to polynomial by caching results.
Recurrence:f(amount) = min(1 + f(amount - coin)) for all coins, with memoization
def coinChange(coins, amount):
memo = {}
def dfs(rem):
if rem == 0:
return 0
if rem < 0:
return float('inf')
if rem in memo:
return memo[rem]
res = float('inf')
for coin in coins:
res = min(res, 1 + dfs(rem - coin))
memo[rem] = res
return res
ans = dfs(amount)
return ans if ans != float('inf') else -1
if __name__ == '__main__':
print(coinChange([1,2,5], 11)) # 3
Line Notes
memo = {}Initialize cache to store results for amounts
if rem in memo:Return cached result if available
memo[rem] = resStore computed result to avoid recomputation
res = min(res, 1 + dfs(rem - coin))Try all coins and pick minimum
ans = dfs(amount)Start recursion from target amount
return ans if ans != float('inf') else -1Return -1 if no solution
import java.util.*;
public class CoinChange {
private static Map<Integer, Integer> memo = new HashMap<>();
public static int coinChange(int[] coins, int amount) {
int res = dfs(coins, amount);
return res == Integer.MAX_VALUE ? -1 : res;
}
private static int dfs(int[] coins, int rem) {
if (rem == 0) return 0;
if (rem < 0) return Integer.MAX_VALUE;
if (memo.containsKey(rem)) return memo.get(rem);
int res = Integer.MAX_VALUE;
for (int coin : coins) {
int sub = dfs(coins, rem - coin);
if (sub != Integer.MAX_VALUE) {
res = Math.min(res, 1 + sub);
}
}
memo.put(rem, res);
return res;
}
public static void main(String[] args) {
System.out.println(coinChange(new int[]{1,2,5}, 11)); // 3
}
}
Line Notes
private static Map<Integer, Integer> memo = new HashMap<>();Cache results for amounts
if (memo.containsKey(rem)) return memo.get(rem);Return cached result if present
memo.put(rem, res);Store computed result
res = Math.min(res, 1 + sub);Try all coins and update minimum
return res;Return minimum coins for rem
#include <iostream>
#include <vector>
#include <unordered_map>
#include <climits>
using namespace std;
unordered_map<int,int> memo;
int dfs(const vector<int>& coins, int rem) {
if (rem == 0) return 0;
if (rem < 0) return INT_MAX;
if (memo.count(rem)) return memo[rem];
int res = INT_MAX;
for (int coin : coins) {
int sub = dfs(coins, rem - coin);
if (sub != INT_MAX) {
res = min(res, 1 + sub);
}
}
memo[rem] = res;
return res;
}
int coinChange(vector<int>& coins, int amount) {
int ans = dfs(coins, amount);
return ans == INT_MAX ? -1 : ans;
}
int main() {
vector<int> coins = {1,2,5};
cout << coinChange(coins, 11) << endl; // 3
return 0;
}
Line Notes
unordered_map<int,int> memo;Cache for storing results
if (memo.count(rem)) return memo[rem];Return cached result if exists
memo[rem] = res;Store computed minimum coins
res = min(res, 1 + sub);Try all coins and update minimum
return ans == INT_MAX ? -1 : ans;Return -1 if no solution
function coinChange(coins, amount) {
const memo = new Map();
function dfs(rem) {
if (rem === 0) return 0;
if (rem < 0) return Infinity;
if (memo.has(rem)) return memo.get(rem);
let res = Infinity;
for (let coin of coins) {
res = Math.min(res, 1 + dfs(rem - coin));
}
memo.set(rem, res);
return res;
}
const ans = dfs(amount);
return ans === Infinity ? -1 : ans;
}
console.log(coinChange([1,2,5], 11)); // 3
Line Notes
const memo = new Map();Cache results for amounts
if (memo.has(rem)) return memo.get(rem);Return cached result if available
memo.set(rem, res);Store computed result
res = Math.min(res, 1 + dfs(rem - coin));Try all coins and update minimum
return ans === Infinity ? -1 : ans;Return -1 if no solution
TimeO(amount * n) where n is number of coins due to memoization
SpaceO(amount) for memo and recursion stack
Each amount is computed once and stored, reducing exponential calls to linear in amount
💡 For amount=20 and 3 coins, this reduces calls from millions to about 60.
Interview Verdict: Accepted
This approach is efficient enough for typical interview constraints and demonstrates DP memoization well.