最长回文子串和最长回文子序列-返回相应字符串

一、题目
所谓回文字符串,就是一个字符串,从左到右读和从右到左读是完全一样的,比如 “a”、“aba”、“abba”。

对于一个字符串,其子串是指连续的一段子字符串,而子序列是可以非连续的一段子字符串。

最长回文子串 和 最长回文子序列(Longest Palindromic Subsequence)是指任意一个字符串,它说包含的长度最长的回文子串和回文子序列。

例如:字符串 “ABCDDCEFA”,它的 最长回文子串 即 “CDDC”,最长回文子序列 即 “ACDDCA”。

二、最长回文子串

  1. 思路
    首先这类问题通过穷举的办法,判断是否是回文子串并再筛选出最长的,效率是很差的。我们使用 动态规划 的策略来求解它。首先我们从子问题入手,并将子问题的解保存起来,然后在求解后面的问题时,反复的利用子问题的解,可以极大的提示效率。

由于最长回文子串是要求连续的,所以我们可以假设 j 为子串的起始坐标,i 为子串的终点坐标,其中 i 和 j 都是大于等于 0 并且小于字符串长度 length 的,且 j <= i,这样子串的长度就可以使用 i - j + 1 表示了。

我们从长度为 1 的子串依次遍历,长度为 1 的子串肯定是回文的,其长度就是 1;然后是长度为 2 的子串依次遍历,只要 str[i] 等于 str[j] ,它就是回文的,其长度为 2;接下来就好办了,长度大于 2 的子串,如果它要满足是回文子串的性质,就必须有 str[i] 等于 str[j] ,并且去掉两头的子串 str[j+1 … i-1] 也一定是回文子串,所以我们使用一个数组来保存以 j 为子串起始坐标,i 为子串终点坐标的子串是否是回文的,由于我们是从子问题依次增大求解的,所以求解 [i … j] 的问题时,比它规模更小的问题结果都是可以直接使用的了。
2. 代码

#include<bits/stdc++.h>
using namespace std;

string Getsub(string str)//最长回文子串
{
    int len =str.size();
    int start=0;
    int maxlen=1;
    bool dp[len][len]={0};//记录坐标为i,j是否为回文串,是则为1,否则为0
    for(int i=0;i<len;i++)
    {
        dp[i][i]=1;
    }
    for(int i=0;i<len;i++)
    {
        for(int j=0;j<=i;j++)
        {
            if(i-j<2)//子串长度小等于2必为回文串
            {
                dp[j][i]=(str[i]==str[j]);
            }else
            {
                dp[j][i]=dp[j+1][i-1] && (str[i]==str[j]);//如果子串为回文并且当前i,j相等则为回文
            }
            if(dp[j][i] && (i-j+1)>maxlen)//修改最长回文的长度和起始
            {
                start = j;
                maxlen=i-j+1;

            }
        }
    }

    return str.substr(start,maxlen);//获取从start开始的,maxlen个字符
}
int main()
{
    string str;
    cin>>str;
    cout<<Getsub(str)<<endl;
    return 0;
}

三、最长回文子序列

  1. 思路
    子序列的问题将比子串更复杂,因为它是可以不连续的,这样如果穷举的话,问题规模将会变得非常大,我们依旧是选择使用 动态规划 来解决。

首先我们假设 str[0 … n-1] 是给定的长度为 n 的字符串,我们使用 lps(0, n-1) 表示以 0 为起始坐标,长度为 n-1 的最长回文子序列的长度。那么我们需要从子问题开始入手,即我们一次遍历长度 1 到 n-1 的子串,并将子串包含的 最长回文子序列的长度 保存在 lps 的二维数组中。

遍历过程中,回文子序列的长度一定有如下性质:

如果子串的第一个元素 str[j] 和最后一个元素 str[i+j] 相等,那么 lps[j, i+j] = lps[j+1, i+j-1] + 2,其中 lps[j+1, i+j-1] 表示去掉两头元素的最长子序列长度。
如果两端的元素不相等,那么lps[j, i+j] = max(lps[j][i+j-1], lps[j+1][i+j]),这两个表示的分别是去掉末端元素的子串和去掉起始元素的子串。
2. 代码

#include<bits/stdc++.h>
using namespace std;
string Getsubseq(string str)//最长回文子序列
{
    int len=str.size();//字符串长度
    //第一维是指起始坐标,第二维是指长度
    int dp[len][len]={0};
    //单个字符长度的最长回文子序列长度为1

    char s[len];
    memset(s,0,sizeof(s));

    for(int i=0;i<len;i++)
    {
        dp[i][1]=1;
    }
    //i代表子串的长度,j代表子串的起始位置
    for(int i=1;i<len;i++)
    {
        for(int j=0;j+i<len;j++)
        {
            if(str[j]==str[i+j])//子串头尾是否相等
            {
                dp[j][i+j]=dp[j+1][i+j-1]+2;//相等则等于减去此头尾的子串的长度+2
            }else
            {
                dp[j][i+j]=max(dp[j+1][i+j],dp[j][i+j-1]);//不相等则等于去除头或尾的最长的子序列
            }
        }
    }
    return dp[0][len-1];
}

int main()
{
    string str;
    cin>>str;
    cout<<Getsubseq(str)<<endl;
    return 0;
}

上面的最长子序列只实现了求的最长子序列的长度,若要得到子序列的字符串则可以使用下面的方法

使用一个字符串数组记录子序列中出现的字符
char s[len];
memset(s,0,sizeof(s));
s[j+i]=s[j]=str[j]

#include<bits/stdc++.h>
using namespace std;

string Getsubseq(string str)//最长回文子序列
{
    int len=str.size();//字符串长度
    //第一维是指起始坐标,第二维是指长度
    int dp[len][len]={0};
    //单个字符长度的最长回文子序列长度为1

    char s[len];
    memset(s,0,sizeof(s));

    for(int i=0;i<len;i++)
    {
        dp[i][1]=1;
    }
    //i代表子串的长度,j代表子串的起始位置
    for(int i=1;i<len;i++)
    {
        for(int j=0;j+i<len;j++)
        {
            if(str[j]==str[i+j])//子串头尾是否相等
            {
                dp[j][i+j]=dp[j+1][i+j-1]+2;//相等则等于减去此头尾的子串的长度+2
                s[j+i]=s[j]=str[j];//创建一个string记录头尾相等的字符
            }else
            {
                dp[j][i+j]=max(dp[j+1][i+j],dp[j][i+j-1]);//不相等则等于去除头或尾的最长的子序列
            }
        }
    }
    string ss;
    for(int i=0;i<len;i++)
    {
        if(s[i]!=0)
            ss.push_back(s[i]);
    }
    cout<<ss.size()<<endl;//此处返回最长子序列的长度也可以使用dp[0][len-1]得到
    return ss;
}
int main()
{
    string str;
    cin>>str;
    cout<<Getsubseq(str)<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值