💡 Memoization avoids recomputing the same subproblems by caching results, drastically improving efficiency.
Intuition
Store results of recursive calls in a table keyed by coin index and amount to reuse them.
Algorithm
- Initialize a memo table to store results for each (coin index, amount).
- If amount is 0, return 1.
- If amount < 0 or no coins left, return 0.
- If result in memo, return it to avoid recomputation.
- Otherwise, compute ways by excluding and including current coin, store in memo.
💡 Memoization converts exponential recursion into polynomial time by caching.
Recurrence:ways(i, amount) = ways(i-1, amount) + ways(i, amount - coins[i])
def count_ways_memo(coins, n, amount, memo=None):
if memo is None:
memo = {}
if amount == 0:
return 1
if amount < 0 or n == 0:
return 0
if (n, amount) in memo:
return memo[(n, amount)]
memo[(n, amount)] = count_ways_memo(coins, n - 1, amount, memo) + count_ways_memo(coins, n, amount - coins[n - 1], memo)
return memo[(n, amount)]
# Driver code
coins = [1, 2, 5]
amount = 5
print(count_ways_memo(coins, len(coins), amount))
Line Notes
memo = {}Initialize memo dictionary to cache results
if (n, amount) in memo:Check if result already computed to avoid recomputation
memo[(n, amount)] = ...Store computed result in memo
return memo[(n, amount)]Return cached or newly computed result
import java.util.HashMap;
import java.util.Map;
public class Solution {
static Map<String, Integer> memo = new HashMap<>();
public static int countWaysMemo(int[] coins, int n, int amount) {
if (amount == 0) return 1;
if (amount < 0 || n == 0) return 0;
String key = n + "," + amount;
if (memo.containsKey(key)) return memo.get(key);
int res = countWaysMemo(coins, n - 1, amount) + countWaysMemo(coins, n, amount - coins[n - 1]);
memo.put(key, res);
return res;
}
public static void main(String[] args) {
int[] coins = {1, 2, 5};
int amount = 5;
System.out.println(countWaysMemo(coins, coins.length, amount));
}
}
Line Notes
static Map<String, Integer> memoMemo map to cache results keyed by coin index and amount
if (memo.containsKey(key))Return cached result if available
memo.put(key, res);Store computed result in memo
return res;Return computed or cached result
#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;
unordered_map<string, int> memo;
int countWaysMemo(int coins[], int n, int amount) {
if (amount == 0) return 1;
if (amount < 0 || n == 0) return 0;
string key = to_string(n) + "," + to_string(amount);
if (memo.find(key) != memo.end()) return memo[key];
memo[key] = countWaysMemo(coins, n - 1, amount) + countWaysMemo(coins, n, amount - coins[n - 1]);
return memo[key];
}
int main() {
int coins[] = {1, 2, 5};
int amount = 5;
int n = sizeof(coins) / sizeof(coins[0]);
cout << countWaysMemo(coins, n, amount) << endl;
return 0;
}
Line Notes
unordered_map<string, int> memo;Memo map to cache results
if (memo.find(key) != memo.end())Return cached result if found
memo[key] = ...Store computed result in memo
return memo[key];Return cached or computed result
const memo = new Map();
function countWaysMemo(coins, n, amount) {
if (amount === 0) return 1;
if (amount < 0 || n === 0) return 0;
const key = n + ',' + amount;
if (memo.has(key)) return memo.get(key);
const res = countWaysMemo(coins, n - 1, amount) + countWaysMemo(coins, n, amount - coins[n - 1]);
memo.set(key, res);
return res;
}
const coins = [1, 2, 5];
const amount = 5;
console.log(countWaysMemo(coins, coins.length, amount));
Line Notes
const memo = new Map();Memo map to cache results
if (memo.has(key))Return cached result if available
memo.set(key, res);Store computed result in memo
return res;Return cached or computed result
TimeO(n * amount) due to memoization caching each state once
SpaceO(n * amount) for memo table and recursion stack
Memoization ensures each (coin index, amount) pair is computed once.
💡 For amount=1000 and 10 coins, this means about 10,000 computations, which is efficient.
Interview Verdict: Accepted
Memoization is a practical optimization that makes the solution efficient enough for interviews.