zoukankan      html  css  js  c++  java
  • 最长回文子序列、串 回文子序列、串个数

    回文子序列和子串的定义参考:http://www.cnblogs.com/AndyJee/p/4465696.html

      子序列:下标可以不连续

      子串:下标连续

    最长回文子序列:dp[i][j]表示从下标i到j的字符串中,最长回文子序列的长度。

    动态规划的状态转移方程为:

    设字符串为str,长度为n,dp[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])

    下面代码打印回文子序列最大长度和对应的回文子序列。

    #include <iostream>
    #include <string>
    #include <cstring>
    
    using namespace std;
    
    #define N 100
    //最长回文子序列的长度
    int f[N][N] = { 0 };
    int f_cur[N] = { 0 };
    int f_pre[N] = { 0 };
    int path[N] = { 0 };
    //回文子序列的个数
    int num[N][N] = { 0 };
    
    int main()
    {
        string s;
        cin >> s;
        int n = s.length();
    
        for( int i = 0; i < n; i++ )
        {
            f[i][i] = 1;
        }
    
        for( int i = n-1; i >= 0; i-- )
        {
            for( int j = i+1; j < n; j++ )
            {
                if( s[i] == s[j] )
                {
                    f[i][j] = f[i+1][j-1] + 2;
                }
                else
                {
                    f[i][j] = max( f[i+1][j], f[i][j-1] );
                }
                //cout << "test: " << "i: " << i << " j: " << j << " f[i][j]: " << f[i][j] << endl;
            }
        }
    
        //尝试着像背包问题那样降低维度,但行不通
        //1.由上面的代码可知,求f[I][j]时,需要f[I+1][j],f[I+1][j-1],这两个是迭代上一个i值(I+1)时得到的结果
        //2.除此之外,求f[I][j]还需要f[I][j-1],这个是迭代当前i值(I)时得到的结果。
        //如果像背包问题那样降低维度,
        //由1,j应该按照从大到小的顺序。因为要使用f[I+1][j-1],保证f'[j-1]是原来的,对应上一个i值
        //由2,j应该按照从小到大的顺序。因为要使用f[I][j-1],保证f'[j-1]是现在这一次迭代的新值,
        //所以要有辅助空间,参考 http://blog.csdn.net/u012243115/article/details/41010913,只保存上一个i值的结果
    
        for( int i = n-1; i >= 0; i-- )
        {
            f_cur[i] = 1;
            for( int j = i+1; j < n; j++ )
            {
                if( s[i] == s[j] )
                {
                    if( i+1 > j-1 ) //对应于上面代码中当i+1>j-1时f[i+1][j-1]默认为0,这里需要手动判断一下
                                    //所以最好把子问题表达式写出来,考虑各种情况
                    {
                        f_cur[j] = 2;
                    }
                    else
                    {
                        f_cur[j] = f_pre[j-1] + 2;
                    }
                }
                else
                {
                    f_cur[j] = max( f_pre[j], f_cur[j-1] );
                }
            }
            //只保存上一个i值的结果
            for( int k = 0; k < n; k++ )
            {
                f_pre[k] = f_cur[k];
            }
        }
    
        //输出最长回文子序列的长度,
        cout << f[0][n-1] << endl;
        cout << f_cur[n-1] << endl;
    
        //如果有多个全部输出,( 如果重复的不输出(set) )
        for( int i_ind = 0; i_ind < n; i_ind++ )
        {
            for( int j_ind = i_ind+1; j_ind < n; j_ind++ )
            {
                if( f[i_ind][j_ind] == f[0][n-1] )
                {
                    memset( path, 0, sizeof( path ) );  //cstring
                    int i = i_ind, j = j_ind;
                    while( i <= j )
                    {
                        if( i == j )
                        {
                            path[i] = 1;
                            break;
                        }
                        //if( f[i][j] == f[i+1][j-1]+2 ) //这样判断不对
                        if( s[i] == s[j] )
                        {
                            path[i] = 1;
                            path[j] = 1;
                            i++;
                            j--;
                        }
                        else if( f[i][j] == f[i+1][j] )
                        {
                            i++;
                        }
                        else if( f[i][j] == f[i][j-1] )
                        {
                            j--;
                        }
                    }
                    for( int i = 0; i <= n; i++ )
                    {
                        if( path[i] == 1 )
                        {
                            cout << s[i] << " ";
                        }
                    }
                    cout << endl;
                }
            }
        }

    求回文子序列个数同样是DP思想,num[i][j]代表从下标i到j的字符串中,回文子序列的个数。同样考虑i>j, i=j, i<j的情况。

      i<j:如果s[i] != s[j],num[i][j] = num[i+1][j] + num[i][j-1] - num[i+1][j-1];

         如果s[i] == s[j],num[i][j] = num[i+1][j] + num[i][j-1] - num[i+1][j-1] + num[i+1][j-1] + 1

                      = num[i+1][j] + num[i][j-1] + 1;  

           后面加的这个 num[i+1][j-1] 是 “s[i] + s[i+1]~s[j-1]的回文子序列 + s[j]” 所组成的新回文子序列,最后的1是指还需要加上“s[i]s[j]”这个新回文子序列。

    代码如下:

    for( int i = n-1; i >= 0; i-- )
        {
            num[i][i] = 1;
            for( int j = i+1; j < n; j++ )
            {
                if( s[i] != s[j] )
                {
                    num[i][j] = num[i+1][j] + num[i][j-1] - num[i+1][j-1];
                }
                else
                {
                    num[i][j] = num[i+1][j] + num[i][j-1] + 1;
                }
            }
        }
        //输出回文子序列的个数
        cout << num[0][n-1] << endl;

    ————————————————————————————————————————————————————————————————————————

    接下来是最长回文子串的长度。p[i][j]表示从下标i到下标j的字符串是否为回文串,

      i>j时,p[i][j] = 0,其实没有用到

      i==j时,p[i][j] = 1;

      i < j 时,如果s[i]==s[j],且s[i]与s[j]之间的字符串也为回文串,即p[i+1][j-1]==1时,p[i][j]=1;

           但当s[i]==s[j],i 与 j 相邻时,无法用上一条规则判断,此时p[i][j]应该为1

           当s[i]!=s[j],p[i][j]=0

    我们可以用变量将最大长度与起始下标记录下来。(其实也可以利用最长回文子序列的思想,令p[i][j]表示从i到j的字符串中包含的最长回文子串的长度,f[i][j]表示s[i]到s[j]字符串是否为回文串,不过不如以上方式直观)

    代码如下:

    int v_max = 0;
        int begin = 0, end = 0;
    
        for( int i = n-1; i >= 0; i-- )
        {
            p[i][i] = 1;
            for( int j = i+1; j < n; j++ )
            {
                if( s[i] == s[j] )
                {
                    if( i+1 == j || p[i+1][j-1] > 0 )
                    {
                        p[i][j] = 1;
                        if( j-i+1 > v_max )
                        {
                            v_max = j-i+1;
                            begin = i;
                            end = j;
                        }
                    }
                }
                else
                {
                    p[i][j] = 0;
                }
            }
        }
        //最长回文子串的长度
        cout << "---" << v_max << endl;
        //最长回文子串
        for( int i = begin; i <= end; i++ )
        {
            cout << s[i] << " ";
        }
       //回文子串的个数
        int cnt = 0;
        for( int i = 0; i < n; i++ )
        {
            for( int j = i; j<n; j++ )
            {
                if( p[i][j] )
                {
                    cnt++;
                }
            }
        }

     因为p[i][j]表示从下标i到下标j的字符串是否为回文串,且求p[i][j]只依赖于p[i+1][j-1],这里只用一个一维数度代替p[i][j]。

     因为这一轮的p[j]依赖上一轮的p[j-1],所以j要从大到小进行计算。

     如果是 这一轮的p[j]依赖这一轮的p[j-1],那就j就要从小到大进行计算。

    int p[50];
    int find( string &s )
    {
        int slen = 1;
        //int begin = 0; int end = 0;
        int n = s.size();
        for( int i = n-1; i >= 0; i-- )
        {
            p[i] = 1;
            for( int j = n-1; j >=i+1; j-- )
            {
                if( s[i] == s[j] )
                {
                    if( j == i+1 || p[j-1] == 1 )
                    {
                        p[j] = 1;
                    }
                    if( slen < j - i + 1 )
                    {
                        slen = j - i + 1;
                        //begin = i;
                        //end = j;
                    }
                }
                else
                {
                    p[j] = 0;
                }
            }
        }
        cout << slen << endl;
        return slen;
    }

    回文子串的个数:

    像上面一样,p[i][j]代表从下标i到j的串是否为回文子串。 记录其个数。

    代码:

    int countSubstrings(string s)
    {
        int n = s.length();
        bool dp[n][n];
        memset( dp, false, n*n*sizeof( bool ) );
    
        int ret = 0;
    
        for( int i = n-1; i >= 0; i-- )
        {
            dp[i][i] = true;
            ret++;
            for( int j = i+1; j < n; j++ )
            {
                if( s[i] == s[j]  )
                {
                    if( i+1 == j || dp[i+1][j-1] )
                    {
                        dp[i][j] = true;
                        ret++;
                    }
                    else
                        dp[i][j] = false;
                }
                else
                {
                    dp[i][j] = false;
                }
            }
        }
        return ret;
    }

    还有一种马拉车算法,用来求最大回文子串的长度,O(n )

    http://www.jianshu.com/p/799bc53d4e3d

    http://blog.csdn.net/u012102306/article/details/51353040

    代码1:

    int getLongestPalindrome(string s, int nn)
    {
        int ret = 0;
        int n = s.length();
        //先扩充, #1#1#2#1#1#
        string ss = "#";
        for( int i = 0; i < n; i++ )
        {
            ss += s[i];
            ss += "#";
        }
    
        //求p数组,保存的为以对应下标字符为中心,
        //最多向右多少个位置(包括中心),使得中心左右这些位置是回文串。
        n = ss.length();
        int p[n];
    
        //mx为目前为止,最多向右扩充到了哪个下标
        //id为对应的中心的下标
        int mx = 0, id = 0;
    
        for( int i = 1; i < n; i++ )
        {
            //当i小于mx时,可以利用已求得的最长的回文串
            if( i < mx )
            {
                //以id为中心,i对应的下标为j
                int j = 2*id - i;
                //以p[j]为中心的回文串,位于以id为中心的回文串中时
                //以p[i]为中心的回文串,也位于以id为中心的回文串中,不需要继续探索
                if( p[j] < mx - i )
                {
                    p[i] = p[j];
                }
                //以p[j]为中心的回文串,位于以id为中心的回文串之外时
                //以p[i]为中心的回文串,至少会向右扩充到mx,需要继续探索
                else if( p[j] > mx - i )
                {
                    p[i] = mx - i;
                    while( i+p[i] < n && i-p[i] >= 0 && ss[i+p[i]] == ss[i-p[i]] )
                        p[i]++;
                }
                //以p[j]为中心的回文串,刚好位于以id为中心的回文串之内
                //以p[i]为中心的回文串,至少会向右扩充到mx,需要继续探索
                else if( p[j] == mx - i )
                {
                    p[i] = mx - i;
                    while( i+p[i] < n && i-p[i] >= 0 && ss[i+p[i]] == ss[i-p[i]] )
                        p[i]++;
                }
            }
            //当i>=mx时
            else
            {
                p[i] = 1;
                while( i+p[i] < n && i-p[i] >= 0 && ss[i+p[i]] == ss[i-p[i]] )
                    p[i]++;
            }
    
            if( i + p[i] -1 > mx )
            {
                mx = i + p[i] -1;
                id = i;
            }
            
            //p[ id ] - 1:以下标为id对应的字符为中心,最大回文子串的长度
            ret = max( ret, p[i] - 1 );
        }
    
        return ret;
    }

    代码2:

    int getLongestPalindrome(string s, int nn)
    {
        int ret = 0;
        int n = nn;
        //先扩充, #1#1#2#1#1#
        string ss = "#";
        for( int i = 0; i < n; i++ )
        {
            ss += s[i];
            ss += "#";
        }
    
        //求p数组,保存的为以对应下标字符为中心,
        //最多向右多少个位置(包括中心),使得中心左右这些位置是回文串。
        n = ss.length();
        int p[n];
    
        //mx为目前为止,最多向右扩充到了哪个下标
        //id为对应的中心的下标
        int mx = 0, id = 0;
        
        for( int i = 1; i < n; i++ )
        {
            //用一行代码表示
            p[i] = i < mx ? min( p[2*id-i], mx-i ) : 1;
    
            while( i+p[i] < n && i-p[i] >= 0 && ss[i+p[i]] == ss[i-p[i]] )
                p[i]++;
    
            if( i + p[i] - 1 > mx )
            {
                mx = i + p[i] -1;
                id = i;
            }
    
            ret = max( ret, p[i] - 1 );
        }
    
        return ret;
    }
  • 相关阅读:
    【ELK】ELK安装与配置
    【Python】函数参数类型及用法
    【ansible】使用ansible安装nginx
    【Gitlab+Jenkins+Ansible】构建自动化部署
    【阿里云文档】常用文档整理
    【SHELL】Linux下安装Oracle Client
    extjs3 tree 指定内容qtip
    ionic3自定义单选
    extjs grid grouping 关闭和展开
    ionic 文本添加清除功能
  • 原文地址:https://www.cnblogs.com/wangzhiyi/p/6670701.html
Copyright © 2011-2022 走看看