💡 Memoization caches results of subproblems to avoid repeated calculations, drastically improving performance. It transforms the exponential brute force into a polynomial time solution by remembering answers.
Intuition
Store results of dfs(left, right) calls in a table so that repeated calls return instantly.
Algorithm
- Initialize a 2D memo table with -1 indicating uncomputed states.
- Modify the recursive function to check memo before computing.
- If memo[left][right] is computed, return it immediately.
- Otherwise, compute as before and store the result in memo.
💡 Memoization turns exponential recursion into polynomial by reusing results.
def maxCoins(nums):
nums = [1] + nums + [1]
n = len(nums)
memo = [[-1] * n for _ in range(n)]
def dfs(left, right):
if left + 1 == right:
return 0
if memo[left][right] != -1:
return memo[left][right]
max_coins = 0
for i in range(left + 1, right):
coins = nums[left] * nums[i] * nums[right]
coins += dfs(left, i) + dfs(i, right)
max_coins = max(max_coins, coins)
memo[left][right] = max_coins
return max_coins
return dfs(0, n - 1)
# Example usage
if __name__ == '__main__':
print(maxCoins([3,1,5,8])) # Output: 167
Line Notes
memo = [[-1] * n for _ in range(n)]Create memo table initialized to -1 to mark unvisited states
if memo[left][right] != -1:Return cached result if already computed
memo[left][right] = max_coinsStore computed result to avoid recomputation
return memo[left][right]Return memoized result for current subproblem
public class BurstBalloons {
private int[][] memo;
public int maxCoins(int[] nums) {
int n = nums.length + 2;
int[] newNums = new int[n];
newNums[0] = 1;
newNums[n - 1] = 1;
for (int i = 0; i < nums.length; i++) {
newNums[i + 1] = nums[i];
}
memo = new int[n][n];
for (int i = 0; i < n; i++) {
Arrays.fill(memo[i], -1);
}
return dfs(newNums, 0, n - 1);
}
private int dfs(int[] nums, int left, int right) {
if (left + 1 == right) return 0;
if (memo[left][right] != -1) return memo[left][right];
int maxCoins = 0;
for (int i = left + 1; i < right; i++) {
int coins = nums[left] * nums[i] * nums[right];
coins += dfs(nums, left, i) + dfs(nums, i, right);
maxCoins = Math.max(maxCoins, coins);
}
memo[left][right] = maxCoins;
return maxCoins;
}
public static void main(String[] args) {
BurstBalloons solution = new BurstBalloons();
System.out.println(solution.maxCoins(new int[]{3,1,5,8})); // 167
}
}
Line Notes
memo = new int[n][n];Initialize memo table for caching subproblem results
Arrays.fill(memo[i], -1);Mark all states as uncomputed
if (memo[left][right] != -1) return memo[left][right];Return cached result if available
memo[left][right] = maxCoins;Store computed result for reuse
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
class Solution {
vector<vector<int>> memo;
public:
int maxCoins(vector<int>& nums) {
int n = nums.size() + 2;
vector<int> newNums(n, 1);
for (int i = 0; i < nums.size(); i++) {
newNums[i + 1] = nums[i];
}
memo.assign(n, vector<int>(n, -1));
return dfs(newNums, 0, n - 1);
}
private:
int dfs(vector<int>& nums, int left, int right) {
if (left + 1 == right) return 0;
if (memo[left][right] != -1) return memo[left][right];
int maxCoins = 0;
for (int i = left + 1; i < right; i++) {
int coins = nums[left] * nums[i] * nums[right];
coins += dfs(nums, left, i) + dfs(nums, i, right);
maxCoins = max(maxCoins, coins);
}
memo[left][right] = maxCoins;
return maxCoins;
}
};
int main() {
Solution sol;
vector<int> nums = {3,1,5,8};
cout << sol.maxCoins(nums) << endl; // 167
return 0;
}
Line Notes
memo.assign(n, vector<int>(n, -1));Initialize memo table with -1 to mark unvisited states
if (memo[left][right] != -1) return memo[left][right];Return cached result if computed
memo[left][right] = maxCoins;Store computed result for current subproblem
return memo[left][right];Return memoized result
function maxCoins(nums) {
nums = [1, ...nums, 1];
const n = nums.length;
const memo = Array.from({length: n}, () => Array(n).fill(-1));
function dfs(left, right) {
if (left + 1 === right) return 0;
if (memo[left][right] !== -1) return memo[left][right];
let maxCoins = 0;
for (let i = left + 1; i < right; i++) {
let coins = nums[left] * nums[i] * nums[right];
coins += dfs(left, i) + dfs(i, right);
maxCoins = Math.max(maxCoins, coins);
}
memo[left][right] = maxCoins;
return maxCoins;
}
return dfs(0, n - 1);
}
console.log(maxCoins([3,1,5,8])); // 167
Line Notes
const memo = Array.from({length: n}, () => Array(n).fill(-1));Create 2D memo array initialized to -1
if (memo[left][right] !== -1) return memo[left][right];Return cached result if available
memo[left][right] = maxCoins;Cache computed result for reuse
return memo[left][right];Return memoized result
TimeO(n^3)
SpaceO(n^2) for memo table + O(n) recursion stack
There are O(n^2) subproblems and each tries O(n) balloons to burst last.
💡 For n=100, this means about 1 million operations, which is feasible.
Interview Verdict: Accepted
Memoization makes the solution efficient enough for typical constraints.