🧠
Top-Down DP with Memoization
💡 Memoization avoids recomputing the same intervals by caching results, drastically improving efficiency while preserving the recursive intuition.
Intuition
Store results of subproblems dp[i][j] so that when the same interval is encountered again, return the cached result instead of recomputing.
Algorithm
- Initialize a 2D memo array to store results for intervals.
- Modify the recursive function to check memo before computing.
- If result exists in memo, return it immediately.
- Otherwise, compute recursively and store the result in memo.
💡 Memoization transforms exponential recursion into polynomial time by remembering past results.
Recurrence:dp[i][j] = max(piles[i] - dp[i+1][j], piles[j] - dp[i][j-1])
def stoneGame(piles):
n = len(piles)
memo = [[None]*n for _ in range(n)]
def dfs(i, j):
if i == j:
return piles[i]
if memo[i][j] is not None:
return memo[i][j]
left = piles[i] - dfs(i + 1, j)
right = piles[j] - dfs(i, j - 1)
memo[i][j] = max(left, right)
return memo[i][j]
return dfs(0, n - 1) > 0
# Driver code
if __name__ == '__main__':
print(stoneGame([5,3,4,5])) # True
Line Notes
memo = [[None]*n for _ in range(n)]Create memo table to cache results for intervals
if memo[i][j] is not None:Return cached result if available to avoid recomputation
memo[i][j] = max(left, right)Store computed result for interval i to j
return dfs(0, n - 1) > 0Check if first player can win based on computed difference
public class Solution {
private Integer[][] memo;
public boolean stoneGame(int[] piles) {
int n = piles.length;
memo = new Integer[n][n];
return dfs(piles, 0, n - 1) > 0;
}
private int dfs(int[] piles, int i, int j) {
if (i == j) return piles[i];
if (memo[i][j] != null) return memo[i][j];
int left = piles[i] - dfs(piles, i + 1, j);
int right = piles[j] - dfs(piles, i, j - 1);
memo[i][j] = Math.max(left, right);
return memo[i][j];
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.stoneGame(new int[]{5,3,4,5})); // true
}
}
Line Notes
private Integer[][] memo;Memo table to store results for intervals
if (memo[i][j] != null) return memo[i][j];Return cached result if computed before
memo[i][j] = Math.max(left, right);Cache the computed max difference for interval
return dfs(piles, 0, n - 1) > 0;Determine if first player can win
#include <iostream>
#include <vector>
#include <climits>
using namespace std;
class Solution {
vector<vector<int>> memo;
public:
int dfs(vector<int>& piles, int i, int j) {
if (i == j) return piles[i];
if (memo[i][j] != INT_MIN) return memo[i][j];
int left = piles[i] - dfs(piles, i + 1, j);
int right = piles[j] - dfs(piles, i, j - 1);
memo[i][j] = max(left, right);
return memo[i][j];
}
bool stoneGame(vector<int>& piles) {
int n = piles.size();
memo.assign(n, vector<int>(n, INT_MIN));
return dfs(piles, 0, n - 1) > 0;
}
};
int main() {
Solution sol;
vector<int> piles = {5,3,4,5};
cout << boolalpha << sol.stoneGame(piles) << endl; // true
return 0;
}
Line Notes
vector<vector<int>> memo;2D memo table to cache results
if (memo[i][j] != INT_MIN) return memo[i][j];Return cached result if available
memo.assign(n, vector<int>(n, INT_MIN));Initialize memo with sentinel values
memo[i][j] = max(left, right);Store computed max difference for interval
var stoneGame = function(piles) {
const n = piles.length;
const memo = Array.from({length: n}, () => Array(n).fill(null));
function dfs(i, j) {
if (i === j) return piles[i];
if (memo[i][j] !== null) return memo[i][j];
const left = piles[i] - dfs(i + 1, j);
const right = piles[j] - dfs(i, j - 1);
memo[i][j] = Math.max(left, right);
return memo[i][j];
}
return dfs(0, n - 1) > 0;
};
console.log(stoneGame([5,3,4,5])); // true
Line Notes
const memo = Array.from({length: n}, () => Array(n).fill(null));Initialize memo table with nulls
if (memo[i][j] !== null) return memo[i][j];Return cached result if computed
memo[i][j] = Math.max(left, right);Cache computed max difference for interval
return dfs(0, n - 1) > 0;Check if first player can win
TimeO(n^2)
SpaceO(n^2) for memo + O(n) recursion stack
Each interval i,j is computed once and stored, reducing exponential calls to quadratic.
💡 For n=500, this means about 250,000 computations, which is efficient enough.
Interview Verdict: Accepted
Memoization makes the solution efficient and practical for interview constraints.