1. Top-Down Recursion with 2D Memoization
Intuition
Define helper(i, j) as the length of LPS in s[i...j]. Base cases: if i > j, return 0; if i == j, return 1. If s[i] == s[j], return 2 + helper(i + 1, j - 1). Otherwise, return max(helper(i + 1, j), helper(i, j - 1)). Memoize with a 2D array memo[i][j].
When to apply: When constructing the intuitive divide-and-conquer base solution.
Algorithm
- Initialize
memomatrix of size N x N filled with -1. - Helper function
solve(i, j): - - If
i > j: return 0. - - If
i == j: return 1. - - If
memo[i][j] != -1: returnmemo[i][j]. - - If
s[i] == s[j]:memo[i][j] = 2 + solve(i + 1, j - 1). - - Else:
memo[i][j] = max(solve(i + 1, j), solve(i, j - 1)). - - Return
memo[i][j]. - Return
solve(0, n - 1).
Top-Down Recursion with 2D Memoization
2 of 2 questions left todayObservation: Initial call lps(0, 4) covers the entire string 'bbbab'
Observation: Initial call lps(0, 4) covers the entire string 'bbbab'.
Decision: Characters at both endpoints match: s[0] == s[4] == b. Recurse to 2 + lps(1, 3) on 'bba'.
Invariant: Matching endpoints contribute +2 to the maximum palindrome length.
#include <iostream>#include <string>#include <vector>#include <algorithm> class Solution { int solve(int i, int j, const std::string& s, std::vector<std::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]) { return memo[i][j] = 2 + solve(i + 1, j - 1, s, memo); } else { return memo[i][j] = std::max(solve(i + 1, j, s, memo), solve(i, j - 1, s, memo)); } }public: int longestPalindromeSubseq(std::string s) { int n = s.size(); std::vector<std::vector<int>> memo(n, std::vector<int>(n, -1)); return solve(0, n - 1, s, memo); }}; int main() { Solution sol; std::cout << sol.longestPalindromeSubseq("bbbab") << std::endl; // 4 std::cout << sol.longestPalindromeSubseq("cbbd") << std::endl; // 2 return 0;}Time & Space Complexity
- Time complexity:
O(N^2)— There are N * (N + 1) / 2 unique states, each computed in O(1) time. - Space complexity:
O(N^2) memo + O(N) recursion stack
Why Brute Force Doesn't Scale:
Interval DP with 1D compression maintains optimal O(N2) time while cutting space from O(N2) to O(N).
Using a single 1000-element array consumes under 4 KB of RAM.