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