算法导论 — 思考题15-2 最长回文子序列

最长回文子序列)回文(palindrome)是正序与逆序相同的非空字符串。例如,所有长度为1的字符串、civic、racecar、aibohphobia都是回文。设计高效算法,求给定输入字符串的最长回文子序列。例如,给定输入character,算法应该返回carac。算法的运行时间是怎样的?
  
  
  假设有一个字符串 S [ 1.. n ] = s 1 , s 2 , … , s n S[1..n] = {s_1, s_2, …, s_n} S[1..n]=s1,s2,,sn P [ 1.. k ] = p 1 , p 2 , … , p k P[1..k] = {p_1, p_2, …, p_k} P[1..k]=p1,p2,,pk S S S的任意最长回文子序列(Longest Palindrome Subsequence, LPS)。
  比较首字符 s 1 s_1 s1和尾字符 s n s_n sn,可以分以下3种情况:
  1) 如果 s 1 = s n s_1 = s_n s1=sn,则有 p 1 = s 1 p_1 = s_1 p1=s1 p k = s n p_k = s_n pk=sn,并且 P [ 2.. k − 1 ] P[2..k-1] P[2..k1] S [ 2.. n − 1 ] S[2..n-1] S[2..n1]的一个LPS;
  2) 如果 s 1 ≠ s n s_1 ≠ s_n s1̸=sn,那么 s 1 ≠ p 1 s_1 ≠ p_1 s1̸=p1意味着 P [ 1.. k ] P[1..k] P[1..k] S [ 2.. n ] S[2..n] S[2..n]的一个LPS;
  3) 如果 s 1 ≠ s n s_1 ≠ s_n s1̸=sn,那么 s n ≠ p k s_n ≠ p_k sn̸=pk意味着 P [ 1.. k ] P[1..k] P[1..k] S [ 1.. n − 1 ] S[1..n-1] S[1..n1]的一个LPS。
  需要补充说明一下。根据上文第(1)点给出的结论,如果 s 1 = s n s_1 = s_n s1=sn,只需要求解 S [ 2.. n − 1 ] S[2..n-1] S[2..n1]的LPS,并将 s 1 s_1 s1 s n s_n sn分别加到 S [ 2.. n − 1 ] S[2..n-1] S[2..n1]的LPS的头部和尾部,从而得到 S [ 1.. n ] S[1..n] S[1..n]的一个回文子序列,我们假设这个回文子序列的长度为 l 1 l_1 l1。然而,严格来说,还需要求解 S [ 1.. n − 1 ] S[1..n-1] S[1..n1] S [ 2.. n ] S[2..n] S[2..n]的LPS,它们也是 S [ 1.. n ] S[1..n] S[1..n]的回文子序列,假设这两个LPS的长度分别为 l 2 l_2 l2 l 3 l_3 l3。我们需要比较 l 1 l_1 l1 l 2 l_2 l2 l 3 l_3 l3的大小,其中最大者对应的回文子序列才是 S [ 1.. n ] S[1..n] S[1..n]的LPS。然而,上文第(1)点为何断言 l 1 l_1 l1一定是最大的?我们采用反证法来说明。
  假设 l 1 &lt; l 2 l_1 &lt; l_2 l1<l2,并假设 S [ 2.. n − 1 ] S[2..n-1] S[2..n1]的LPS的长度为 l 4 l_4 l4,有 l 1 = l 4 + 2 l_1 = l_4 + 2 l1=l4+2。如果 S [ 1.. n − 1 ] S[1..n-1] S[1..n1]的LPS不包含 s 1 s_1 s1,那么将 s 1 s_1 s1 S [ 1.. n − 1 ] S[1..n-1] S[1..n1]中去掉, S [ 1.. n − 1 ] S[1..n-1] S[1..n1]的LPS实际上也是 S [ 2.. n − 1 ] S[2..n-1] S[2..n1]的LPS,故 l 2 = l 4 l_2 = l_4 l2=l4,于是有 l 1 = l 2 + 2 &gt; l 2 l_1 = l_2 + 2 &gt; l_2 l1=l2+2>l2。这与 l 1 &lt; l 2 l_1 &lt; l_2 l1<l2矛盾。
  如果 S [ 1.. n − 1 ] S[1..n-1] S[1..n1]的LPS包含 s 1 s_1 s1,那么 S [ 1.. n − 1 ] S[1..n-1] S[1..n1]的LPS的尾字符一定等于 s 1 s_1 s1,将 S [ 1.. n − 1 ] S[1..n-1] S[1..n1]的LPS的首尾字符去掉,得到的子序列一定是 S [ 2.. n − 1 ] S[2..n-1] S[2..n1]的一个回文子序列(假设它的长度为 l 5 l_5 l5,有 l 2 = l 5 + 2 l_2 = l_5 + 2 l2=l5+2),并且一定有 l 4 ≥ l 5 l_4 ≥ l_5 l4l5,故 l 1 = l 4 + 2 ≥ l 5 + 2 = l 2 l_1 = l_4 + 2 ≥ l_5 + 2 = l_2 l1=l4+2l5+2=l2,这也与 l 1 &lt; l 2 l_1 &lt; l_2 l1<l2矛盾。故 l 1 ≥ l 2 l_1 ≥ l_2 l1l2是一定成立的。
  同理, l 1 ≥ l 3 l_1 ≥ l_3 l1l3也是成立的。综上所述,上文结论(1)是对的。
  可以看到,LPS问题与最长公共子序列问题是很类似的,也具有最优子结构。
  • 如果 s 1 = s n s_1 = s_n s1=sn,那么需要求解子问题 S [ 2.. n − 1 ] S[2..n-1] S[2..n1]的LPS,并将 s 1 s_1 s1 s n s_n sn分别加到 S [ 2.. n − 1 ] S[2..n-1] S[2..n1]的LPS的头部和尾部,从而得到 S [ 1.. n ] S[1..n] S[1..n]的LPS
  • 如果 s 1 ≠ s n s_1 ≠ s_n s1̸=sn,那么需要分别求解两个子问题 S [ 2.. n ] S[2..n] S[2..n] S [ 1.. n − 1 ] S[1..n-1] S[1..n1]的LPS,二者中较长者即为 S [ 1.. n ] S[1..n] S[1..n]的LPS。
  用 c [ i , j ] c[i, j] c[i,j]表示子序列 S [ i , j ] S[i, j] S[i,j]的LPS的长度,可以得到以下递归式:
        在这里插入图片描述
  我们可以自下而上的求解最长回文子序列问题。下面给出该算法的伪代码。
  在这里插入图片描述
  与LCS类似,LPS的时间复杂度为 Θ ( n 2 ) Θ(n^2) Θ(n2)。下图展示了对字符串character运行了LPS-LENGTH的结果。
  在这里插入图片描述
  本节相关的code链接。
  https://github.com/yangtzhou2012/Introduction_to_Algorithms_3rd/tree/master/Chapter15/Problems/Problem_15-2

最长回文子序列(Longest Palindromic Subsequence,LPS)问题是指在一个给定的字符串中找到一个最长回文子序列回文子序列是指一个序列本身不是回文串,但它是一个回文串的子序列。 在C++中,我们可以使用动态规划(Dynamic Programming,DP)的方法来解决这个问题。动态规划的主要思想是将一个大问题分解成小问题,然后从小问题出发,逐渐求得大问题的解。 以下是一个使用动态规划解决最长回文子序列问题的C++示例代码: ```cpp #include <iostream> #include <vector> #include <string> using namespace std; // 函数用于计算字符串str的最长回文子序列的长度 int longestPalindromeSubseq(string str) { int n = str.size(); // 创建一个二维数组dp,用于存储子问题的解,初始化所有值为0 vector<vector<int>> dp(n, vector<int>(n, 0)); // 单个字符的最长回文子序列长度为1,所以对角线上的元素设置为1 for (int i = 0; i < n; i++) { dp[i][i] = 1; } // 如果两个字符相同,那么它俩组成的子序列长度为2 for (int cl = 2; cl <= n; cl++) { for (int i = 0; i < n - cl + 1; i++) { int j = i + cl - 1; if (str[i] == str[j] && cl == 2) { dp[i][j] = 2; } else if (str[i] == str[j]) { dp[i][j] = dp[i + 1][j - 1] + 2; } else { dp[i][j] = max(dp[i][j - 1], dp[i + 1][j]); } } } // 返回整个字符串的最长回文子序列长度 return dp[0][n - 1]; } int main() { string str; cout << "请输入一个字符串:" << endl; cin >> str; cout << "最长回文子序列的长度为:" << longestPalindromeSubseq(str) << endl; return 0; } ``` 在这段代码中,`dp[i][j]`表示从字符串的第`i`个字符到第`j`个字符组成的子串的最长回文子序列的长度。通过初始化对角线以及递推式逐步填充这个二维数组,最终可以得到整个字符串的最长回文子序列长度。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值