zoukankan      html  css  js  c++  java
  • 最长的回文子序列

    给一个字符串,找出它的最长的回文子序列的长度。例如,如果给定的序列是“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)为去掉最后一个元素。

    有了上面的公式,可以很容易的写出下面的递归程序:

    2)重叠子问题

    画出上面程序的递归树(部分),已一个长度为6 的字符串为例:

                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)。该问题符合动态规划的两个主要性质: 重叠子问题 和 最优子结构 。

    方法一:递归解法,可用备忘录法(变种动态规划)改进

    #include<stdio.h>
    #include<string.h>
    int lps(char *seq, int i, int j)
    {
        //一个元素即为1
        if (i == j)
            return 1;
        if (i > j) return 0; //因为只计算序列 seq[i ... j]
    
        // 如果首尾相同
        if (seq[i] == seq[j])
            return lps(seq, i + 1, j - 1) + 2;
    
        // 首尾不同
        return max(lps(seq, i, j - 1), lps(seq, i + 1, j));
    }
    
    /* 测试 */
    int main()
    {
        char seq[] = "acmerandacm";
        int n = strlen(seq);
        printf("The length of the LPS is %d", lps(seq, 0, n - 1));
        getchar();
        return 0;
    }

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

    方法二:动态规划:自下而上的打表解法:

    下面通过动态规划的方法解决,通过自下而上的方式打表,存储子问题的最优解。

    int lpsDp(char * str, int n){
        int dp[n][n], tmp;
        memset(dp, 0, sizeof(dp));
        for (int i = 0; i<n; i++) dp[i][i] = 1;
        // i 表示 当前长度为 i+1的 子序列
        for (int i = 1; i<n; i++){
            tmp = 0;
            //考虑所有连续的长度为i+1的子串. 该串为 str[j, j+i]
            for (int j = 0; j + i<n; j++){
                //如果首尾相同
                if (str[j] == str[j + i]){
                    tmp = dp[j + 1][j + i - 1] + 2;
                }
                else{
                    tmp = max(dp[j + 1][j + i], dp[j][j + i - 1]);
                }
                dp[j][j + i] = tmp;
            }
        }
        //返回串 str[0][n-1] 的结果
        return dp[0][n - 1];
    }


    该算法的时间复杂度为O(n^2)。其实这个问题和 最长公共子序列 问题有些相似之处,我们可以对LCS算法做些修改,来解决此问题:

    1 对给定的字符串逆序 存储在另一个数组 rev[] 中

    2 再求这两个 字符串的 LCS的长度

    时间复杂度也为 O(n^2)。

     

  • 相关阅读:
    Java实现 LeetCode 432 全 O(1) 的数据结构
    Java实现 LeetCode 432 全 O(1) 的数据结构
    Makefile 自动生成依赖
    比较详细的利用虚拟机对SD卡FAT32+EXT4+Ext4分区图解教程
    STM32F0308开发环境的选择--CooCox CoIDE篇
    linux下包管理命令yum与apt-get以及开发环境配置
    裸机编程与OS环境编程的有关思考
    Eclipse C/C++环境配置
    Linux Eclipse代码提示功能设置(Java & C/C++)
    Linux下高效编写Shell——shell特殊字符汇总
  • 原文地址:https://www.cnblogs.com/laiqun/p/5707252.html
Copyright © 2011-2022 走看看