🧠
Memoization (Top-Down DP)
💡 Memoization caches results of recursive calls to avoid repeated work, drastically improving efficiency.
Intuition
Store results of subproblems defined by day, transactions left, and holding state to reuse instead of recomputing.
Algorithm
- Create a 3D cache keyed by day, transactions left, and holding state.
- Modify recursive function to check cache before computing.
- If cached, return stored result.
- Otherwise compute, store in cache, and return.
💡 Memoization turns exponential recursion into polynomial time by avoiding repeated calculations.
def maxProfit(k, prices):
n = len(prices)
from functools import lru_cache
@lru_cache(None)
def dfs(i, transactions_left, holding):
if i == n or transactions_left == 0:
return 0
if holding:
sell = prices[i] + dfs(i + 1, transactions_left - 1, False)
hold = dfs(i + 1, transactions_left, True)
return max(sell, hold)
else:
buy = -prices[i] + dfs(i + 1, transactions_left, True)
skip = dfs(i + 1, transactions_left, False)
return max(buy, skip)
return dfs(0, k, False)
# Example usage
if __name__ == '__main__':
print(maxProfit(2, [3,2,6,5,0,3])) # Output: 7
Line Notes
@lru_cache(None)Caches results of dfs calls to avoid recomputation
def dfs(i, transactions_left, holding):Recursive function with memoization
if i == n or transactions_left == 0:Base case to stop recursion
if holding:Branch when holding stock to decide sell or hold
sell = prices[i] + dfs(i + 1, transactions_left - 1, False)Sell stock and reduce transactions
hold = dfs(i + 1, transactions_left, True)Hold stock without selling
return max(sell, hold)Return best profit when holding stock
else:Branch when not holding stock to decide buy or skip
buy = -prices[i] + dfs(i + 1, transactions_left, True)Buy stock and change holding state
skip = dfs(i + 1, transactions_left, False)Skip buying on this day
return max(buy, skip)Return best profit when not holding stock
return dfs(0, k, False)Start recursion with no stock held
import java.util.*;
public class Solution {
private Integer[][][] memo;
public int maxProfit(int k, int[] prices) {
int n = prices.length;
memo = new Integer[n][k + 1][2];
return dfs(prices, 0, k, 0);
}
private int dfs(int[] prices, int i, int transactionsLeft, int holding) {
if (i == prices.length || transactionsLeft == 0) return 0;
if (memo[i][transactionsLeft][holding] != null) return memo[i][transactionsLeft][holding];
int result;
if (holding == 1) {
int sell = prices[i] + dfs(prices, i + 1, transactionsLeft - 1, 0);
int hold = dfs(prices, i + 1, transactionsLeft, 1);
result = Math.max(sell, hold);
} else {
int buy = -prices[i] + dfs(prices, i + 1, transactionsLeft, 1);
int skip = dfs(prices, i + 1, transactionsLeft, 0);
result = Math.max(buy, skip);
}
memo[i][transactionsLeft][holding] = result;
return result;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.maxProfit(2, new int[]{3,2,6,5,0,3})); // 7
}
}
Line Notes
private Integer[][][] memo;3D memo array to cache results by day, transactions left, and holding
if (i == prices.length || transactionsLeft == 0) return 0;Base case for recursion termination
if (memo[i][transactionsLeft][holding] != null) return memo[i][transactionsLeft][holding];Return cached result if available
if (holding == 1) {Branch when holding stock to decide sell or hold
int sell = prices[i] + dfs(prices, i + 1, transactionsLeft - 1, 0);Sell stock and reduce transactions
int hold = dfs(prices, i + 1, transactionsLeft, 1);Hold stock without selling
result = Math.max(sell, hold);Choose max profit when holding
} else {Branch when not holding stock to decide buy or skip
int buy = -prices[i] + dfs(prices, i + 1, transactionsLeft, 1);Buy stock and change holding state
int skip = dfs(prices, i + 1, transactionsLeft, 0);Skip buying on this day
result = Math.max(buy, skip);Choose max profit when not holding
memo[i][transactionsLeft][holding] = result;Store computed result in cache
return dfs(prices, 0, k, 0);Start recursion with no stock held
System.out.println(sol.maxProfit(2, new int[]{3,2,6,5,0,3}));Test example output
#include <iostream>
#include <vector>
using namespace std;
class Solution {
vector<vector<vector<int>>> memo;
public:
int dfs(const vector<int>& prices, int i, int transactionsLeft, int holding) {
if (i == prices.size() || transactionsLeft == 0) return 0;
if (memo[i][transactionsLeft][holding] != -1) return memo[i][transactionsLeft][holding];
int result;
if (holding == 1) {
int sell = prices[i] + dfs(prices, i + 1, transactionsLeft - 1, 0);
int hold = dfs(prices, i + 1, transactionsLeft, 1);
result = max(sell, hold);
} else {
int buy = -prices[i] + dfs(prices, i + 1, transactionsLeft, 1);
int skip = dfs(prices, i + 1, transactionsLeft, 0);
result = max(buy, skip);
}
return memo[i][transactionsLeft][holding] = result;
}
int maxProfit(int k, vector<int>& prices) {
int n = prices.size();
memo.assign(n, vector<vector<int>>(k + 1, vector<int>(2, -1)));
return dfs(prices, 0, k, 0);
}
};
int main() {
Solution sol;
vector<int> prices = {3,2,6,5,0,3};
cout << sol.maxProfit(2, prices) << endl; // 7
return 0;
}
Line Notes
vector<vector<vector<int>>> memo;3D vector for memoization cache
if (i == prices.size() || transactionsLeft == 0) return 0;Base case to stop recursion
if (memo[i][transactionsLeft][holding] != -1) return memo[i][transactionsLeft][holding];Return cached result if computed
if (holding == 1) {Branch when holding stock to decide sell or hold
int sell = prices[i] + dfs(prices, i + 1, transactionsLeft - 1, 0);Sell stock and reduce transactions
int hold = dfs(prices, i + 1, transactionsLeft, 1);Hold stock without selling
result = max(sell, hold);Choose max profit when holding
} else {Branch when not holding stock to decide buy or skip
int buy = -prices[i] + dfs(prices, i + 1, transactionsLeft, 1);Buy stock and change holding state
int skip = dfs(prices, i + 1, transactionsLeft, 0);Skip buying on this day
result = max(buy, skip);Choose max profit when not holding
return memo[i][transactionsLeft][holding] = result;Store and return computed result
cout << sol.maxProfit(2, prices) << endl;Print result for example
function maxProfit(k, prices) {
const n = prices.length;
const memo = new Array(n).fill(0).map(() =>
new Array(k + 1).fill(0).map(() => new Array(2).fill(null)));
function dfs(i, transactionsLeft, holding) {
if (i === n || transactionsLeft === 0) return 0;
if (memo[i][transactionsLeft][holding] !== null) return memo[i][transactionsLeft][holding];
let result;
if (holding) {
const sell = prices[i] + dfs(i + 1, transactionsLeft - 1, 0);
const hold = dfs(i + 1, transactionsLeft, 1);
result = Math.max(sell, hold);
} else {
const buy = -prices[i] + dfs(i + 1, transactionsLeft, 1);
const skip = dfs(i + 1, transactionsLeft, 0);
result = Math.max(buy, skip);
}
memo[i][transactionsLeft][holding] = result;
return result;
}
return dfs(0, k, 0);
}
// Example usage
console.log(maxProfit(2, [3,2,6,5,0,3])); // 7
Line Notes
const memo = new Array(n).fill(0).map(() =>Initialize 3D memo array with nulls
if (i === n || transactionsLeft === 0) return 0;Base case to end recursion
if (memo[i][transactionsLeft][holding] !== null) return memo[i][transactionsLeft][holding];Return cached result if available
if (holding) {Branch when holding stock to decide sell or hold
const sell = prices[i] + dfs(i + 1, transactionsLeft - 1, 0);Sell stock and reduce transactions
const hold = dfs(i + 1, transactionsLeft, 1);Hold stock without selling
result = Math.max(sell, hold);Choose max profit when holding
} else {Branch when not holding stock to decide buy or skip
const buy = -prices[i] + dfs(i + 1, transactionsLeft, 1);Buy stock and change holding state
const skip = dfs(i + 1, transactionsLeft, 0);Skip buying on this day
result = Math.max(buy, skip);Choose max profit when not holding
memo[i][transactionsLeft][holding] = result;Cache computed result
return dfs(0, k, 0);Start recursion with no stock held
console.log(maxProfit(2, [3,2,6,5,0,3]));Print example output
TimeO(n * k * 2) = O(nk) due to memoization of states
SpaceO(n * k * 2) for memo cache and recursion stack
Each day, transaction count, and holding state combination is computed once
💡 For n=1000 and k=10, this means about 20,000 computations, which is feasible.
Interview Verdict: Accepted
Memoization makes the brute force approach efficient enough for large inputs.