关于在字符串中获得所有的回文子序列的数量及最长回文子字符串问题

 多年前发表在网易博客上的文章,今天转过来。

       今天刚刚完成了微软"编程之美”网上编程挑战赛。hihoCode平台上的第二个赛题就是针对此问题。刚开始思考这个问题,真的是一头雾水,摸不着道。在网上找了一下资料,发现针对这个问题的解决,网上只有台湾国立暨南大学资讯系的一位研究生的论文里面有。但是,仔细阅读完论文(全英文),才发现,他的算法用程序语言实现起来,其实真的很难。

       在这里,我就把用到的所有知识、我的思路和一些查到的资料分享给大家。

方法一、(台湾的那个研究生的思路)

       Step1:利用分治法或动态规划法,找到这个字符串中最长的回文子序列,记下它的长度,记为Longest。(对于求Longest的具体方法,王晓东的《算法设计与分析》这本书上有详细的解答)

      Step2:然后用遍历的方法找出字符串中所有可以构成回文子序列的1—pair回文序列,如“fdsf”中的“ff”,并进行序号标记。for(i=0;i<str.length();i++)  for(j=i+1;j<str.length();j++)  if(str[i]==str[j])  Label[i][j]=1;(当然,所有的单字符本身是一个回文序列,1—pair回文序列中间的所有单字符又使1—pair变成1-1-1式的回文字符序列)

      Step3:之后,将符合条件的1-pair不断嵌入到另一个1-pair中,直到Longest/2-pair.条件符合是指若有1-pair回文子序列(i,j)和(m,n),当m>i&&j<n时,即可将(m,n)插入到(i,j)中,从而构成2-pair回文子序列。然后找出所有这样的可以匹配成为2-pair的回文序列。之后,再由2-pair回文序列为基础,加入另外的可以嵌入到2-pair的1-pair回文序列,构成3-pair回文序列,直到Longest/2-pair为止。(当然,所有的回文序列中间的单字符加上回文序列也是回文序列)

方法二、(由字符串中的最长回文子序列的思路拓展得到)

        如上方法一所说,对于求Longest的具体方法,王晓东的《算法设计与分析》这本书上有详细的解答。那么,如何拓展呢??(动态规划法或者是分治法)

        我们知道,最长回文子序列是采用规避两端,考虑中间的策略。即若有字符串string,长度为length,找出字符串string[i:j]之间的最长回文子序列,则有f(i,j)为获取函数:

       当i>j时,f(i,j)=0。

       当i=j时,f(i,j)=1。

       当i<j并且string[i]=string[j]时,f(i,j)=f(i+1,j-1)+2。

       当i<j并且string[i]≠string[j]时,f(i,j)=max( f(i,j-1), f(i+1,j) )。

      注意如果i+1=j并且string[i]=string[j]时,f(i,j)=f(i+1,j-1)+2=f(j,j-1)+2=2,这就是“当i>j时f(i,j)=0”的好处。

     由于f(i,j)依赖i+1,所以循环计算的时候,第一维必须倒过来计算,从string.length-1到0。

      最后,string的最长回文子序列长度为f(0, string.length-1)。

      好了,由这个方法拓展,怎么拓呢??设Get(i,j)为获取字符串string[i:j]中间所有回文子序列的函数

      (1)对于string[i:j]来说,若有string[i]==string[j],则Get(i,j)由此几部分组成-------string[i]、string[j]、Get(i+1,j-1)、Get(i,j-1)、Get(i+1,j)、string[i]-string[j]、string[i]-Get(i+1,j-1)-string[j](因为最两头已回文,故与Get(i+1,j-1)相同,是比Get(i+1,j-1)长2个字符的回文子序列);

      (2)若有string[i]!=string[j],则Get(i,j)由此几部分组成-------string[i]、string[j]、Get(i+1,j-1)、Get(i,j-1)、Get(i+1,j);

      (3)if(i==j)Get(i,j)==1;

        但是在(1)(2)中,Get(i,j-1)、Get(i+1,j)同时又包含Get(i+1,j-1),且Get(i,j-1)包含string[i],Get(i+1,j)包含string[j]。

       因此,实际上,当string[i]==string[j],则Get(i,j)=Get(i,j-1)+Get(i+1,j)+1;

                                   当string[i]!=string[j],则Get(i,j)=Get(i,j-1)+Get(i+1,j)-Get(i+1,j-1);

       动态规划和分治思路是一样的,只不过实现不一样。

由分治法得到代码:

#include<stdio.h>
#include<math.h>
#include<string.h>

int LPS_Develope(char *Str,int i,int j)//利用分治法,找出所有回文子序列的个数
{
    if(i==j)return 1;
    else if(i>j)return 0;
    if(Str[i]==Str[j])
        return 1+LPS_Develope(Str,i+1,j)+LPS_Develope(Str,i,j-1);
    else
        return LPS_Develope(Str,i+1,j)+LPS_Develope(Str,i,j-1)-LPS_Develope(Str,i+1,j-1);
}

int main()
{
    char str[30];
    int scan_T,i=0,length=0,Sum=0;
    scanf("%d",&scan_T);
    for(i=0; i<scan_T; i++)
    {
        scanf("%s",str);
        length=strlen(str);
        Sum=LPS_Develope(str,0,length-1);
        if(i!=scan_T-1)
        printf("Case #%d: %d\n",i+1,Sum);
        else
        printf("Case #%d: %d",i+1,Sum);
    }
}

使用动态规划方法(由分治转换):

#include<stdio.h>
#include<math.h>
#include<string.h>
int main(void)

{

    char str[30];

    int scan_T,i=0,j=0,m=0,length=0,Sum=0;

    int str_m[25][25];

    scanf("%d",&scan_T);

    for(i=0; i<scan_T; i++)

    {

        scanf("%s",str);

        length=strlen(str);

//        Sum=LPS_Develope(str,0,length-1);

        memset(str_m,0,sizeof(str_m));

        for(j=length-1; j>=0; j--)

        {                 //利用动态规划法求解

            str_m[j][j]=1;//由分治法思路转换

            for(m=j+1; m<length; m++)

            {

                if(str[j]==str[m])

                    str_m[j][m]=1+str_m[j+1][m]+str_m[j][m-1];

                else

                    str_m[j][m]=str_m[j+1][m]+str_m[j][m-1]-str_m[j+1][m-1];

            }

        }

        if(i!=scan_T-1)

            printf("Case #%d: %d\n",i+1,str_m[0][length-1]);

        else

            printf("Case #%d: %d",i+1,str_m[0][length-1]);

    }

    return 0;

}

下面更新于2018.8.17.

而针对字符串中的最长回文字符串问题,可以参考这篇文章https://www.cnblogs.com/coderJiebao/p/Algorithmofnotes30.html。同样C++实现代码(转载)。

//最长回文字符串
int get_max(string str)
{
    int length = str.length();
    int matrix[length][length];
    memset(matrix,0,sizeof(matrix));
    int max_length = 1;
    for (int i = 0; i < length; i++)
    {
        matrix[i][i] = 1;
        if (i < length - 1)
        {
            if (str[i] = str[i+1])
            {
                matrix[i][i+1] = 1;
                max_length = 2;
            }
            else
                matrix[i][i+1] = 0;
        }
    }

    for (int es_len = 3; es_len <= length; es_len++)
        for (int b_index = 0; b_index+es_len-1 < length; b_index++)
        {
            e_index = b_index + es_len - 1;
            if (str[b_index] == str[e_index] && matrix[b_index+1][e_index-1] == 1)
            {
                matrix[b_index][e_index] = 1;
                max_length = es_len;
            }
        }
    return max_length;
}

猜你喜欢

转载自blog.csdn.net/LiuPeiP_VIPL/article/details/81675656
今日推荐