🧠
Top-Down DP with Memoization
💡 Memoization caches results of recursive calls to avoid recomputation, drastically improving efficiency over brute force.
Intuition
Store results of subproblems identified by day, transactions left, and holding state to reuse them instead of recomputing.
Algorithm
- Create a 3D memo table indexed by day, transactions left, and holding state.
- Before recursive call, check if result is cached; if yes, return it.
- Otherwise, compute recursively as in brute force and store result.
- Return the cached or computed result.
💡 Memoization turns exponential recursion into polynomial time by avoiding repeated work.
Recurrence:Same as brute force but with memoization to store f(i,k,holding).
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 = dfs(i + 1, transactions_left - 1, False) + prices[i]
skip = dfs(i + 1, transactions_left, True)
return max(sell, skip)
else:
buy = dfs(i + 1, transactions_left, True) - prices[i]
skip = dfs(i + 1, transactions_left, False)
return max(buy, skip)
return dfs(0, k, False)
if __name__ == '__main__':
print(maxProfit(2, [3,2,6,5,0,3])) # 7
Line Notes
@lru_cache(None)Caches results of dfs calls to avoid recomputation
if i == n or transactions_left == 0:Base case for recursion termination
return max(sell, skip)Choose best action when holding stock
return max(buy, skip)Choose best action 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 res;
if (holding == 1) {
int sell = dfs(prices, i + 1, transactionsLeft - 1, 0) + prices[i];
int skip = dfs(prices, i + 1, transactionsLeft, 1);
res = Math.max(sell, skip);
} else {
int buy = dfs(prices, i + 1, transactionsLeft, 1) - prices[i];
int skip = dfs(prices, i + 1, transactionsLeft, 0);
res = Math.max(buy, skip);
}
memo[i][transactionsLeft][holding] = res;
return res;
}
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
memo = new Integer[n][k + 1][2];Initialize memo table for caching results
if (memo[i][transactionsLeft][holding] != null) return memo[i][transactionsLeft][holding];Return cached result if available
memo[i][transactionsLeft][holding] = res;Store computed result in memo
return dfs(prices, 0, k, 0);Start recursion with no stock held
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
vector<vector<vector<int>>> memo;
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 res;
if (holding == 1) {
int sell = dfs(prices, i + 1, transactionsLeft - 1, 0) + prices[i];
int skip = dfs(prices, i + 1, transactionsLeft, 1);
res = max(sell, skip);
} else {
int buy = dfs(prices, i + 1, transactionsLeft, 1) - prices[i];
int skip = dfs(prices, i + 1, transactionsLeft, 0);
res = max(buy, skip);
}
return memo[i][transactionsLeft][holding] = res;
}
public:
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
memo.assign(n, vector<vector<int>>(k + 1, vector<int>(2, -1)));Initialize memo with -1 to indicate uncomputed states
if (memo[i][transactionsLeft][holding] != -1) return memo[i][transactionsLeft][holding];Return cached result if computed
return memo[i][transactionsLeft][holding] = res;Store and return computed result
return dfs(prices, 0, k, 0);Start recursion with no stock held
function maxProfit(k, prices) {
const n = prices.length;
const memo = new Map();
function dfs(i, transactionsLeft, holding) {
if (i === n || transactionsLeft === 0) return 0;
const key = i + ',' + transactionsLeft + ',' + holding;
if (memo.has(key)) return memo.get(key);
let res;
if (holding) {
const sell = dfs(i + 1, transactionsLeft - 1, false) + prices[i];
const skip = dfs(i + 1, transactionsLeft, true);
res = Math.max(sell, skip);
} else {
const buy = dfs(i + 1, transactionsLeft, true) - prices[i];
const skip = dfs(i + 1, transactionsLeft, false);
res = Math.max(buy, skip);
}
memo.set(key, res);
return res;
}
return dfs(0, k, false);
}
console.log(maxProfit(2, [3,2,6,5,0,3])); // 7
Line Notes
const memo = new Map();Initialize map to cache results
const key = i + ',' + transactionsLeft + ',' + holding;Create unique key for memoization
if (memo.has(key)) return memo.get(key);Return cached result if available
memo.set(key, res);Store computed result in cache
return dfs(0, k, false);Start recursion with no stock held
TimeO(n * k * 2)
SpaceO(n * k * 2) for memo and recursion stack
Each state (day, transactions left, holding) is computed once and cached.
💡 For n=1000 and k=10, this means about 20,000 computations, which is efficient.
Interview Verdict: Accepted
Memoization makes the solution efficient enough for large inputs.