(動態規划)最長回文子序列、回文子序列個數


主要內容:

1、什么是回文?

2、字符子串和字符子序列的區別

3、最長回文子序列的思路和代碼

4、回文子序列個數的思路和代碼

 

1、什么是回文palindrome?

回文指的是正讀和反讀都一樣的字符串,如aba,abba等

2、字符子串和字符子序列的區別

字符字串指的是字符串中連續的n個字符;如palindrome中,pa,alind,drome等都屬於它的字串

而字符子序列指的是字符串中不一定連續但先后順序一致的n個字符;如palindrome中,plind,lime屬於它的子序列,而mod,rope則不是,因為它們與字符串的字符順序不一致。

3、最長回文子序列

要求:

給定字符串,求它的最長回文子序列長度。回文子序列反轉字符順序后仍然與原序列相同。例如字符串abcdfcba中,最長回文子序列長度為7,abcdcba或abcfcba。

思路:

動態規划思想

對於任意字符串,如果頭尾字符相同,那么字符串的最長子序列等於去掉首尾的字符串的最長子序列加上首尾;如果首尾字符不同,則最長子序列等於去掉頭的字符串的最長子序列和去掉尾的字符串的最長子序列的較大者。

因此動態規划的狀態轉移方程為:

設字符串為str,長度為n,p[i][j]表示第i到第j個字符間的子序列的個數(i<=j),則:

狀態初始條件:dp[i][i]=1 (i=0:n-1)

狀態轉移方程:dp[i][j]=dp[i+1][j-1] + 2  if(str[i]==str[j])

                   dp[i][j]=max(dp[i+1][j],dp[i][j-1])  if (str[i]!=str[j])

代碼:

以下代碼中的兩層循環變量i,j的順序可以改變,但必須滿足i<=j的條件。

計算dp[i][j]時需要計算dp[i+1][*]或dp[*][j-1],因此i應該從大到小,即遞減;j應該從小到大,即遞增。

#include <iostream>
#include <vector>

using namespace std;

int longestPalindromeSubSequence1(string str){
    int n=str.length();
    vector<vector<int> > dp(n,vector<int>(n));

    for(int j=0;j<n;j++){
        dp[j][j]=1;
        for(int i=j-1;i>=0;i--){
            if(str[i]==str[j])
                dp[i][j]=dp[i+1][j-1]+2;
            else
                dp[i][j]=max(dp[i+1][j],dp[i][j-1]);
        }
    }
    return dp[0][n-1];
}

int longestPalindromeSubSequence2(string str){
    int n=str.length();
    vector<vector<int> > dp(n,vector<int>(n));

    for(int i=n-1;i>=0;i--){
        dp[i][i]=1;
        for(int j=i+1;j<n;j++){
            if(str[i]==str[j])
                dp[i][j]=dp[i+1][j-1]+2;
            else
                dp[i][j]=max(dp[i+1][j],dp[i][j-1]);
        }
    }
    return dp[0][n-1];
}

int main()
{
    string s;
    int length;
    while(cin>>s){
        length=longestPalindromeSubSequence2(s);
        cout<<length<<endl;
    }
    return 0;
}

image

 

4、回文子序列個數

要求:

給定字符串,求它的回文子序列個數。回文子序列反轉字符順序后仍然與原序列相同。例如字符串aba中,回文子序列為"a", "a", "aa", "b", "aba",共5個。內容相同位置不同的子序列算不同的子序列。

思路:

動態規划思想

對於任意字符串,如果頭尾字符不相等,則字符串的回文子序列個數就等於去掉頭的字符串的回文子序列個數+去掉尾的字符串的回文子序列個數-去掉頭尾的字符串的回文子序列個數;如果頭尾字符相等,那么除了上述的子序列個數之外,還要加上首尾相等時新增的子序列個數,1+去掉頭尾的字符串的回文子序列個數,1指的是加上頭尾組成的回文子序列,如aa,bb等。

因此動態規划的狀態轉移方程為:

設字符串為str,長度為n,p[i][j]表示第i到第j個字符間的最長子序列的長度(i<=j),則:

狀態初始條件:dp[i][i]=1 (i=0:n-1)

狀態轉移方程:dp[i][j]=dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1]  if(str[i]!=str[j])

                   dp[i][j]=dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1]+dp[i+1][j-1]+1=dp[i+1][j] + dp[i][j-1]+1  if (str[i]==str[j])

代碼:

#include <iostream>
#include <vector>
using namespace std;

int NumOfPalindromeSubSequence(string str){
    int len=str.length();
    vector<vector<int> > dp(len,vector<int>(len));

    for(int j=0;j<len;j++){
        dp[j][j]=1;
        for(int i=j-1;i>=0;i--){
            dp[i][j]=dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1];
            if(str[i]==str[j])
                dp[i][j]+=1+dp[i+1][j-1];
        }
    }
    return dp[0][len-1];
}

int main()
{
    string str;
    int num;
    while(cin>>str){
        num=NumOfPalindromeSubSequence(str);
        cout<<num<<endl;
    }
    return 0;
}

 

 

image


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM