💡 Memoization caches results of recursive calls to avoid recomputation, drastically improving efficiency while preserving the recursive intuition.
Intuition
Store results of lps(i, j) in a table so repeated calls return instantly instead of recomputing.
Algorithm
- Initialize a 2D memo table with -1 indicating uncomputed states.
- Recursively compute lps(i, j) using the same logic as brute force.
- Before computing, check if memo[i][j] is already computed and return it if so.
- Store computed results in memo to reuse.
💡 Memoization avoids exponential recomputation by remembering results, turning recursion into efficient DP.
Recurrence:Same as brute force: lps(i, j) = 1 if i == j; 0 if i > j; else if s[i] == s[j] then 2 + lps(i+1, j-1) else max(lps(i+1, j), lps(i, j-1))
def lps(s, i, j, memo):
if i > j:
return 0
if i == j:
return 1
if memo[i][j] != -1:
return memo[i][j]
if s[i] == s[j]:
memo[i][j] = 2 + lps(s, i+1, j-1, memo)
else:
memo[i][j] = max(lps(s, i+1, j, memo), lps(s, i, j-1, memo))
return memo[i][j]
if __name__ == '__main__':
s = 'bbbab'
n = len(s)
memo = [[-1]*n for _ in range(n)]
print(lps(s, 0, n-1, memo))
Line Notes
if memo[i][j] != -1:Return cached result to avoid recomputation
memo[i][j] = 2 + lps(s, i+1, j-1, memo)Store result when ends match
memo[i][j] = max(lps(s, i+1, j, memo), lps(s, i, j-1, memo))Store max result when ends differ
memo = [[-1]*n for _ in range(n)]Initialize memo table with -1 to mark uncomputed
public class Solution {
public static int lps(String s, int i, int j, int[][] memo) {
if (i > j) return 0;
if (i == j) return 1;
if (memo[i][j] != -1) return memo[i][j];
if (s.charAt(i) == s.charAt(j)) {
memo[i][j] = 2 + lps(s, i+1, j-1, memo);
} else {
memo[i][j] = Math.max(lps(s, i+1, j, memo), lps(s, i, j-1, memo));
}
return memo[i][j];
}
public static void main(String[] args) {
String s = "bbbab";
int n = s.length();
int[][] memo = new int[n][n];
for (int[] row : memo) java.util.Arrays.fill(row, -1);
System.out.println(lps(s, 0, n-1, memo));
}
}
Line Notes
if (memo[i][j] != -1) return memo[i][j];Return cached result to avoid recomputation
memo[i][j] = 2 + lps(s, i+1, j-1, memo);Store result when ends match
memo[i][j] = Math.max(lps(s, i+1, j, memo), lps(s, i, j-1, memo));Store max result when ends differ
for (int[] row : memo) java.util.Arrays.fill(row, -1);Initialize memo table with -1
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int lps(const string &s, int i, int j, vector<vector<int>> &memo) {
if (i > j) return 0;
if (i == j) return 1;
if (memo[i][j] != -1) return memo[i][j];
if (s[i] == s[j]) {
memo[i][j] = 2 + lps(s, i+1, j-1, memo);
} else {
memo[i][j] = max(lps(s, i+1, j, memo), lps(s, i, j-1, memo));
}
return memo[i][j];
}
int main() {
string s = "bbbab";
int n = s.size();
vector<vector<int>> memo(n, vector<int>(n, -1));
cout << lps(s, 0, n-1, memo) << endl;
return 0;
}
Line Notes
if (memo[i][j] != -1) return memo[i][j];Return cached result to avoid recomputation
memo[i][j] = 2 + lps(s, i+1, j-1, memo);Store result when ends match
memo[i][j] = max(lps(s, i+1, j, memo), lps(s, i, j-1, memo));Store max result when ends differ
vector<vector<int>> memo(n, vector<int>(n, -1));Initialize memo table with -1
function lps(s, i, j, memo) {
if (i > j) return 0;
if (i === j) return 1;
if (memo[i][j] !== -1) return memo[i][j];
if (s[i] === s[j]) {
memo[i][j] = 2 + lps(s, i+1, j-1, memo);
} else {
memo[i][j] = Math.max(lps(s, i+1, j, memo), lps(s, i, j-1, memo));
}
return memo[i][j];
}
const s = 'bbbab';
const n = s.length;
const memo = Array.from({length: n}, () => Array(n).fill(-1));
console.log(lps(s, 0, n-1, memo));
Line Notes
if (memo[i][j] !== -1) return memo[i][j];Return cached result to avoid recomputation
memo[i][j] = 2 + lps(s, i+1, j-1, memo);Store result when ends match
memo[i][j] = Math.max(lps(s, i+1, j, memo), lps(s, i, j-1, memo));Store max result when ends differ
const memo = Array.from({length: n}, () => Array(n).fill(-1));Initialize memo table with -1
TimeO(n^2)
SpaceO(n^2) for memo + O(n) recursion stack
Each substring state is computed once and stored, reducing exponential calls to polynomial.
💡 For n=1000, this means about 1 million states, which is feasible with memoization.
Interview Verdict: Accepted
Memoization makes the solution efficient enough for typical interview constraints.