Given a string s, find the longest palindromic subsequence’s length ins.
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.
A palindromic sequence means a sequence of characters which is a palindrome. Now, we must understand it clearly that we are talking about a sub sequence and not a substring. More: Subarrays vs Subsequences vs Subsets Definition.
The brute-force approach is to enumerate every possible subsequence and check which ones are palindromic, then return the length of the longest. Since there are about 2^N subsequences for a string of length N, this method is exponential in time and quickly becomes infeasible for large inputs.
We can solve this problem recursively by considering the two ends of the string. If the characters at both ends match, they can be part of the palindrome, and we look for the longest palindromic subsequence in the substring between them. If they don’t match, we try both possibilities—excluding either end—and take the maximum.
classSolution {
public:int longestPalindromeSubseq(const std::string& s) {
returnlps(s, 0, s.size() -1);
}
private:int lps(const std::string& s, int i, int j) {
if (i > j) return0;
if (i == j) return1;
if (s[i] == s[j])
return (i +1== j) ?2:2+ lps(s, i +1, j -1);
return std::max(lps(s, i +1, j), lps(s, i, j -1));
}
};
The plain recursive approach recalculates the same subproblems many times. For e.g. In the below tree, L(1,4) is computed twice, showing the repetitive work that memoization can avoid.
#include<vector>classSolution {
public:int longestPalindromeSubseq(const std::string& s) {
int n = s.size();
std::vector<std::vector<int>> memo(n, std::vector<int>(n, -1));
returnlps(s, 0, n -1, memo);
}
private:int lps(const std::string& s, int i, int j, std::vector<std::vector<int>>& memo) {
if (i > j) return0;
if (i == j) return1;
if (memo[i][j] !=-1) return memo[i][j];
if (s[i] == s[j])
memo[i][j] = (i +1== j) ?2:2+ lps(s, i +1, j -1, memo);
else memo[i][j] = std::max(lps(s, i +1, j, memo), lps(s, i, j -1, memo));
return memo[i][j];
}
};
We can build the solution for the longest palindromic subsequence by solving all smaller substrings first and combining their results. For example, for the string “ACECCBA”, we first fill in all single characters (which are palindromes of length 1), then two-character substrings, and so on, until we solve for the whole string.
classSolution {
public:int longestPalindromeSubseq(const std::string& s) {
int n = s.size();
std::vector<std::vector<int>> dp(n, std::vector<int>(n, 0));
for (int i =0; i < n; ++i) dp[i][i] =1;
for (int len =2; len <= n; ++len) {
for (int i =0; i <= n - len; ++i) {
int j = i + len -1;
if (s[i] == s[j])
dp[i][j] = (len ==2) ?2:2+ dp[i+1][j-1];
else dp[i][j] = std::max(dp[i+1][j], dp[i][j-1]);
}
}
return dp[0][n-1];
}
};
classSolution {
publicintlongestPalindromeSubseq(String s) {
int n = s.length();
int[][] dp =newint[n][n];
for (int i = 0; i < n; ++i) dp[i][i]= 1;
for (int len = 2; len <= n; ++len) {
for (int i = 0; i <= n - len; ++i) {
int j = i + len - 1;
if (s.charAt(i) == s.charAt(j))
dp[i][j]= (len == 2) ? 2 : 2 + dp[i+1][j-1];
else dp[i][j]= Math.max(dp[i+1][j], dp[i][j-1]);
}
}
return dp[0][n-1];
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
classSolution {
funlongestPalindromeSubseq(s: String): Int {
val n = s.length
val dp = Array(n) { IntArray(n) }
for (i in0 until n) dp[i][i] = 1for (len in2..n) {
for (i in0..n-len) {
val j = i + len - 1 dp[i][j] = if (s[i] == s[j]) {
if (len ==2) 2else2 + dp[i+1][j-1]
} else {
maxOf(dp[i+1][j], dp[i][j-1])
}
}
}
return dp[0][n-1]
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
classSolution:
deflongestPalindromeSubseq(self, s: str) -> int:
n = len(s)
dp = [[0] * n for _ in range(n)]
for i in range(n):
dp[i][i] =1for l in range(2, n+1):
for i in range(n-l+1):
j = i + l -1if s[i] == s[j]:
dp[i][j] =2if l ==2else2+ dp[i+1][j-1]
else:
dp[i][j] = max(dp[i+1][j], dp[i][j-1])
return dp[0][n-1]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
impl Solution {
pubfnlongest_palindrome_subseq(s: &str) -> usize {
let n = s.len();
let bytes = s.as_bytes();
letmut dp =vec![vec![0; n]; n];
for i in0..n { dp[i][i] =1; }
for len in2..=n {
for i in0..=n-len {
let j = i + len -1;
dp[i][j] =if bytes[i] == bytes[j] {
if len ==2 { 2 } else { 2+ dp[i+1][j-1] }
} else {
dp[i+1][j].max(dp[i][j-1])
};
}
}
dp[0][n-1]
}
}