動態規劃-最長迴文子序列

出處: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)爲去掉最後一個元素。

有了上面的公式,可以很容易的寫出下面的遞歸程序:

01 #include<stdio.h>
02 #include<string.h>
03 int lps(char *seq, int i, int j)
04 {
05    //一個元素即爲1
06    if (i == j)
07      return 1;
08    if(i > j) return 0; //因爲只計算序列 seq[i ... j]
09  
10    // 如果首尾相同
11    if (seq[i] == seq[j])
12       return lps (seq, i+1, j-1) + 2;
13  
14    // 首尾不同
15    return max( lps(seq, i, j-1), lps(seq, i+1, j) );
16 }
17  
18 /* 測試 */
19 int main()
20 {
21     char seq[] = "acmerandacm";
22     int n = strlen(seq);
23     printf ("The lnegth of the LPS is %d", lps(seq, 0, n-1));
24     getchar();
25     return 0;
26 }

Output: The lnegth of the LPS is 5 (即爲: amama)

重疊子問題

畫出上面程序的遞歸樹(部分),已一個長度爲6 的字符串爲例:

1              L(0, 5)
2            /        \
3           /          \ 
4       L(1,5)          L(0,4)
5      /    \            /    \
6     /      \          /      \
7 L(2,5)    L(1,4)  L(1,4)  L(0,3)

可見有許多重複的計算,例如L(1,4)。該問題符合動態規劃的兩個主要性質: 重疊子問題 和 最優子結構  

下面通過動態規劃的方法解決,通過自下而上的方式打表,存儲子問題的最優解。

01 int lpsDp(char * str,int n){
02     int dp[n][n], tmp;
03     memset(dp,0,sizeof(dp));
04     for(int i=0; i<n; i++) dp[i][i] = 1;
05     // i 表示 當前長度爲 i+1的 子序列
06     for(int i=1; i<n; i++){
07         tmp = 0;
08         //考慮所有連續的長度爲i+1的子串. 該串爲 str[j, j+i]
09         for(int j=0; j+i<n; j++){
10             //如果首尾相同
11             if(str[j] == str[j+i]){
12                 tmp = dp[j+1][j+i-1] + 2;
13             }else{
14                 tmp = max(dp[j+1][j+i],dp[j][j+i-1]);
15             }
16             dp[j][j+i] = tmp;
17         }
18     }
19     //返回串 str[0][n-1] 的結果
20     return dp[0][n-1];
21 }

該算法的時間複雜度爲O(n^2)。其實這個問題和 最長公共子序列 問題有些相似之處,我們可以對LCS算法做些修改,來解決此問題:

1) 對給定的字符串逆序 存儲在另一個數組 rev[] 中

2) 再求這兩個 字符串的 LCS的長度

時間複雜度也爲 O(n^2)。

參考:http://www.geeksforgeeks.org/dynamic-programming-set-12-longest-palindromic-subsequence/

發佈了35 篇原創文章 · 獲贊 7 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章