Longest Palindromic Subsequence
Link: Leetcode - Longest Palindromic Subsequence
Problem Statement
Given a string s, return the length of the longest palindromic subsequence in s.
Intuition
A subsequence is different from a substring. We want the longest sequence that reads the same forward and backward, but characters do not have to be contiguous.
We reduce this problem to finding the Longest Common Subsequence (LCS) between:
-
the original string
s -
and its reversed version
rev(s)
This works because a palindrome is equal to its reverse.
Example
s = "bbabcbcab"
rev = "bacbcbabb"
LCS of s and rev = "babcbab" → length = 7
Answer = 7
Step-by-Step Approaches
We will follow this flow:
1. Recursion
2. Memoization
3. Tabulation
4. Space Optimization
1. Recursion (Top-Down without Memoization)
Java Code (Recursive LCS of s and reverse(s))
class Solution {
public int longestPalindromeSubseq(String s) {
String rev = new StringBuilder(s).reverse().toString();
return lcs(s, rev, s.length(), s.length());
}
private int lcs(String s1, String s2, int i, int j) {
if (i == 0 || j == 0) return 0;
if (s1.charAt(i - 1) == s2.charAt(j - 1))
return 1 + lcs(s1, s2, i - 1, j - 1);
else
return Math.max(lcs(s1, s2, i - 1, j), lcs(s1, s2, i, j - 1));
}
}
2. Memoization (Top-Down with Caching)
Java Code (DP Memoized)
class Solution {
public int longestPalindromeSubseq(String s) {
String rev = new StringBuilder(s).reverse().toString();
int n = s.length();
int[][] dp = new int[n + 1][n + 1];
for (int[] row : dp) Arrays.fill(row, -1);
return lcs(s, rev, n, n, dp);
}
private int lcs(String s1, String s2, int i, int j, int[][] dp) {
if (i == 0 || j == 0) return 0;
if (dp[i][j] != -1) return dp[i][j];
if (s1.charAt(i - 1) == s2.charAt(j - 1))
return dp[i][j] = 1 + lcs(s1, s2, i - 1, j - 1, dp);
else
return dp[i][j] = Math.max(lcs(s1, s2, i - 1, j, dp), lcs(s1, s2, i, j - 1, dp));
}
}
3. Tabulation (Bottom-Up DP)
Java Code
class Solution {
public int longestPalindromeSubseq(String s) {
int n = s.length();
String rev = new StringBuilder(s).reverse().toString();
int[][] dp = new int[n + 1][n + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (s.charAt(i - 1) == rev.charAt(j - 1)) {
dp[i][j] = 1 + dp[i - 1][j - 1];
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[n][n];
}
}
4. Space Optimization
You only need two 1D arrays (previous and current row).
Java Code (Space Optimized)
class Solution {
public int longestPalindromeSubseq(String s) {
int n = s.length();
String rev = new StringBuilder(s).reverse().toString();
int[] prev = new int[n + 1];
int[] curr = new int[n + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (s.charAt(i - 1) == rev.charAt(j - 1)) {
curr[j] = 1 + prev[j - 1];
} else {
curr[j] = Math.max(prev[j], curr[j - 1]);
}
}
prev = curr.clone();
}
return prev[n];
}
}
Time and Space Complexity
| Approach | Time Complexity | Space Complexity |
|---|---|---|
| Recursion | O(2^n) | O(n) (stack) |
| Memoization | O(n^2) | O(n^2) |
| Tabulation | O(n^2) | O(n^2) |
| Space Optimized | O(n^2) | O(n) |
Dry Run
s = "bbbab"
rev = "babbb"
dp LCS matrix will build and final result will be dp[5][5] = 4 (bbbb is a subsequence)
Answer = 4
Summary
-
Problem reduces to finding LCS between
sandreverse(s). -
Tried all four techniques: Recursion → Memoization → Tabulation → Space Optimization.
-
Final answer is length of LCS.