1、最长回文子序列
最长回文子序列(Longest Palindromic Subsequence,简称LPS)是常见的字符串问题之一,它指的是一个字符串中最长的回文子序列。回文指的是正反顺序读都相等的字符串。回文子序列不要求子序列在原字符串中是连续的。
比如,例如字符序列"BBABCBCAB",最长回文子序列是“BACBCAB”(可能不唯一),它的长度是7
2、如何去写其状态转移方程?
LPS问题可以通过动态规划来解决。我们定义状态dp[i][j]表示从字符串第i个字符到第j个字符的最长回文子序列的长度。对于一个长度为n的字符串,最长回文子序列的长度即为dp[0][n-1]。
2.1 初始化
对于长度为1的子串,我们已经初始化了dp[i][i]=1。
对于长度为2的子串,如果两个字符相等,则dp[i][i+1]=2,否则dp[i][i+1]=1。
2.2 状态转移方程
接下来,考虑如何推导状态转移方程。根据回文的定义,如果字符串中起始和结束字符相等,那么这两个字符一定是回文子序列的一部分。因此,我们可以得到以下转移方程:
dp[i][j] = dp[i+1][j-1] + 2 (i ≤ j, s[i] = s[j])
dp[i][j] = max(dp[i+1][j], dp[i][j-1]) (i ≤ j, s[i] ≠ s[j])
上述状态转移方程是基于以下两种情况考虑的:
-
如果字符串的第一个和最后一个字符相等,那么它们一定在最长回文子序列中。
-
如果字符串的第一个和最后一个字符不相等,那么它们两个不可能同时出现在最长回文子序列中。我们需要考虑“缩小问题规模”,去寻找子问题的最优解。
2.3 最终结果
我们可以从长度为1的子串和长度为2的子串开始,逐渐增加子串的长度求解dp[i][j],直到求得dp[0][n-1]为止。最终的结果即为最长回文子序列的长度。
3、代码如下:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1005;
int dp[N][N];
int main() {
string s;
cin >> s;
int n = s.length();
// 初始化
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 + 1; i++) {
int j = i + len - 1;
if (s[i] == s[j]) {
// 情况1
dp[i][j] = dp[i+1][j-1] + 2;
} else {
// 情况2
dp[i][j] = max(dp[i+1][j], dp[i][j-1]);
}
}
}
cout << dp[0][n-1] << endl;
return 0;
}
上述代码中,对于长度为1的子串,我们已经初始化了dp[i][i]=1。对于长度为2的子串,如果两个字符相等,则dp[i][i+1]=2,否则dp[i][i+1]=1。最后,我们逐渐增加子串长度,直到求出dp[0][n-1]的值。
最长回文子序列问题的时间复杂度为O(n^2),空间复杂度也为O(n^2)。