Longest Palindromic Subsequence
Leetcode Daily Challenge (14th April, 2023)
Problem Statement:-
Given a string s
, find the longest palindromic subsequence*'s length in* s
.
A subsequence is a sequence that can be derived from another sequence by deleting some or no elements without changing the order of the remaining elements.
Link: https://leetcode.com/problems/longest-palindromic-subsequence/description/
Problem Explanation with examples:-
Example 1
Input: s = "bbbab"
Output: 4
Explanation: One possible longest palindromic subsequence is "bbbb".
Example 2
Input: s = "cbbd"
Output: 2
Explanation: One possible longest palindromic subsequence is "bb".
Constraints
1 <= s.length <= 1000
s
consists only of lowercase English letters.
Intuition:-
The longest palindromic subsequence is actually the longest common subsequence of the string and its reverse.
The longest common subsequence is a classic dynamic programming problem. The recurrence relation goes like this:
We take two indices, idx1 and idx2, and compare the characters at those indices in the two strings. If they are equal, we add 1 to the result of the function call with idx1 and idx2 decremented by 1. If they are not equal, we take the maximum of the function call with idx1 decremented by 1 and idx2 unchanged, and the function call with idx1 unchanged and idx2 decremented by 1.
The base case is when either of the indices is less than 0. In that case, we return 0.
We can memoize the function call using the functools.cache decorator. This will make the function run in O(n^2) time and O(n^2) space.
For longest palindromic subsequence, we can just call the lcs function with the string and its reverse as the first two arguments, and the length of the string minus 1 as the last two arguments.
Solution:-
Create a recursive lcs function that takes four arguments: s1, s2, idx1, and idx2.
If either of the indices is less than 0, return 0.
If the characters at the indices in the two strings are equal, return 1 plus the result of the function call with idx1 and idx2 decremented by 1.
Otherwise, return the maximum of the function call with idx1 decremented by 1 and idx2 unchanged, and the function call with idx1 unchanged and idx2 decremented by 1.
Create a variable n and set it equal to the length of the string.
Return the result of calling the lcs function with the string and its reverse as the first two arguments, and n minus 1 as the last two arguments.
Code:-
JAVA Solution
class Solution {
public static int lcs_memo(String s1, String s2, int n, int m, int[][] dp){
if(n < 0 || m < 0) return 0;
if(dp[n][m] != -1) return dp[n][m];
if(s1.charAt(n) == s2.charAt(m)) return dp[n][m] = 1 + lcs_memo(s1, s2, n-1, m-1, dp);
else return dp[n][m] = Math.max(lcs_memo(s1, s2, n-1, m, dp), lcs_memo(s1, s2, n, m-1, dp));
}
public int longestPalindromeSubseq(String s) {
String s2 = new StringBuilder(s).reverse().toString();
int n = s.length();
int m = s2.length();
int[][] dp = new int[n][m];
for(int i = 0; i < n; i++) for(int j = 0; j < m; j++) dp[i][j] = -1;
return lcs_memo(s, s2, n-1, m-1, dp);
}
}
Python Solution
class Solution:
def longestPalindromeSubseq(self, s: str) -> int:
n = len(s)
@cache
def lcs(s1, s2, idx1, idx2):
if idx1<0 or idx2<0:
return 0
if s1[idx1] == s2[idx2]:
return 1 + lcs(s1, s2, idx1-1, idx2-1)
return max(lcs(s1, s2, idx1-1, idx2), lcs(s1, s2, idx1, idx2-1))
return lcs(s, s[::-1], n-1, n-1)
Complexity Analysis:-
TIME:-
The time complexity is O(n^2) because we are memoizing the function call.
SPACE:-
The space complexity is O(n^2) + internal recursion stack space because we are memoizing the function call.