出處:http://www.acmerblog.com/longest-palindromic-subsequence-5721.html
給一個字符串,找出它的最長的迴文子序列的長度。例如,如果給定的序列是“BBABCBCAB”,則輸出應該是7,“BABCBAB”是在它的最長迴文子序列。 “BBBBB”和“BBCBB”也都是該字符串的迴文子序列,但不是最長的。注意和最長迴文子串的區別(參考:最長迴文串)!這裏說的子序列,類似最長公共子序列LCS( Longest
Common Subsequence)問題,可以是不連續的。這就是LPS(Longest Palindromic Subsequence)問題。
最直接的解決方法是:生成給定字符串的所有子序列,並找出最長的迴文序列,這個方法的複雜度是指數級的。下面來分析怎麼用動態規劃解決。
1)最優子結構
假設 X[0 ... n-1] 是給定的序列,長度爲n. 讓 L(0,n-1) 表示 序列 X[0 ... n-1] 的最長迴文子序列的長度。
1. 如果X的最後一個元素和第一個元素是相同的,這時:L(0, n-1) = L(1, n-2) + 2 , 還以 “BBABCBCAB” 爲例,第一個和最後一個相同,因此 L(1,n-2) 就表示藍色的部分。
2. 如果不相同:L(0, n-1) = MAX ( L(1, n-1) , L(0, n-2) )。 以”BABCBCA” 爲例,L(1,n-1)即爲去掉第一個元素的子序列,L(0, n-2)爲去掉最後一個元素。
有了上面的公式,可以很容易的寫出下面的遞歸程序:
03 |
int lps( char *seq, int i, int j) |
12 |
return lps
(seq, i+1, j-1) + 2; |
15 |
return max(
lps(seq, i, j-1), lps(seq, i+1, j) ); |
21 |
char seq[]
= "acmerandacm" ; |
23 |
printf ( "The
lnegth of the LPS is %d" ,
lps(seq, 0, n-1)); |
Output: The lnegth of the LPS is 5 (即爲: amama)
重疊子問題
畫出上面程序的遞歸樹(部分),已一個長度爲6 的字符串爲例:
7 |
L(2,5)
L(1,4) L(1,4) L(0,3) |
可見有許多重複的計算,例如L(1,4)。該問題符合動態規劃的兩個主要性質: 重疊子問題 和 最優子結構 。
下面通過動態規劃的方法解決,通過自下而上的方式打表,存儲子問題的最優解。
01 |
int lpsDp( char *
str, int n){ |
03 |
memset (dp,0, sizeof (dp)); |
04 |
for ( int i=0;
i<n; i++) dp[i][i] = 1; |
06 |
for ( int i=1;
i<n; i++){ |
09 |
for ( int j=0;
j+i<n; j++){ |
11 |
if (str[j]
== str[j+i]){ |
12 |
tmp
= dp[j+1][j+i-1] + 2; |
14 |
tmp
= max(dp[j+1][j+i],dp[j][j+i-1]); |
該算法的時間複雜度爲O(n^2)。其實這個問題和 最長公共子序列 問題有些相似之處,我們可以對LCS算法做些修改,來解決此問題:
1) 對給定的字符串逆序 存儲在另一個數組 rev[] 中
2) 再求這兩個 字符串的 LCS的長度
時間複雜度也爲 O(n^2)。
參考:http://www.geeksforgeeks.org/dynamic-programming-set-12-longest-palindromic-subsequence/