DP 最长公共子序列并标记输出

这篇博客详细介绍了如何使用动态规划解决最长公共子序列问题,包括转移方程的建立、二维数组f[][]的初始化及状态更新,并通过标记数组path[][]记录解题路径,最后给出递归打印最长子序列的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

/**
    动规是必考题目,有时甚至考好几道,非常灵活,非常巧妙

    最长公共子序列是动规的经典题目,也被用于好多教材的例子
    谈到动态规划,我对其也是最肤浅的认识,感觉和记忆化搜索
    是不分家的,我一般都是先把整个问题局部化,然后分析局部
    大致写个 转移方程 再验证是否正确并考虑特殊情况,不管怎
    么样,只要经过大量重复练习,神马都是浮云

    求其最长的子序列,如:
    abcdef
    bdaf
    这两个串的最长子序列为 bdf

    子序列和子串不一样,子串必须连续,子序列不需要连续,它
    只抢到字母的先后顺序,如果求最长子串,这就难了,当然可
    以被后缀数组秒杀,后面会贴

    只要提到两个串,然后又和DP 挂钩,大多都是二维数组f[M][M],
    M为串的最大长度,f[i][j] 表示a串前 i的字符和b 串前 j 的字
    符所得到的最优解,逐个递推到f[M][N];

    这个并不好理解,只有自己一遍遍手动模拟,然后才会有感觉,

    char s1[M], s2[M];
    scanf("%s%s", s1+1, s2+1);
    //这样输入可以使下面处理简单点,即s1[1] 为串的第一个字符,而不是s1[0]
    int f[M][M]; //用来保存子问题和问题的结果
    //f 数组初始化为0

    //设l1, l2 为s1, s2串长度
    for (int i=1; i<=l1; i++)
        for (int j=1; j<=l2; j++) {  //之所以从1 开始,因为下面有 f[i-1][j-1]
            if (s1[i] == s2[j])
                f[i][j] = f[i-1][j-1] + 1; //这是显然的
            else
                f[i][j] = max(f[i-1][j], f[i][j-1]); //如果不相等就继承一个最大的
        }
    上面虽然看起来简单,但是对于刚接触的编程的大学生理解起来也是很费劲的
    如果用手也模拟不出来,最好还是本算法书看下具体意思了,相信几乎每本上面
    都有这个例子

    上面f[l1][l2] 给出了最常的公共子序列长度,可是如果想具体打印出来怎么办
    当然得标记下

    int path[M][M]; //这是一个标记数组,总共有3 种情况,所以有3 种取值
    //初始化为0

    for (int i=1; i<=l1; i++)
        for (int j=1; j<=l2; j++) {
            if (s1[i] == s2[j]) {
                path[i][j] = 1;     //这种情况标记为1
                f[i][j] = f[i-1][j-1] + 1;
            }
            else if (f[i-1][j] > f[i][j-1]) {
                path[i][j] = 2;
                f[i][j] = f[i-1][j];
            }
            else {
                path[i][j] = 3;
                f[i][j] = f[i][j-1];
            }

        }

    //现在path 已经标记了所有的步骤,现在来递归输出

    void print(int i, int j) {
        if (path[i][j] == 0)
            return;
        if (path[i][j] == 1) {
            print(i-1, j-1);
            printf("%c ", s1[i]); //当然也可以是 s2[j]
        }
        else if (path[i][j] == 2)
            print(i-1, j);
        else
            print(i, j-1);
    }

    调用 print(l1, l2); 即可输出

    POJ 2250


    睡觉了,睡觉了,晚安
*/

收藏于 2012-01-15
来自于百度空间

<think>嗯,用户问的是最长公共子序列(LCS)算法的实现和解释。我需要先回忆一下LCS的基本概念和动态规划的解决方法。首先,LCS和最长公共子串不同,子序列不要求连续,这点要明确。然后动态规划的思路是构建一个二维数组,记录两个字符串在不同位置上的LCS长度。 用户可能需要具体的步骤解释,比如状态定义、转移方程,以及如何从dp表中回溯得到实际的子序列。代码实现部分,用户提供了Python和Java的引用,所以可能需要用这两种语言中的一种来展示例子。不过根据用户的问题,可能更倾向于Python,因为示例中的快速排序用的是Python。 还要注意用户提到的引用,引用1提到不同的变体,比如最长递增子序列,但这里重点在LCS。引用2和3分别给出了Java和Python的实现,可能需要参考这些内容,但不要直接复制。要确保LaTeX格式正确,行内公式用$...$,独立公式用$$...$$,且正确换行。 另外,用户要求生成相关问题,可能需要考虑LCS的应用场景、时间空间复杂度优化,或者与其他算法的比较。比如和编辑距离的关系,或者不同的变种问题。 需要检查LaTeX语法是否正确,比如动态规划的状态转移方程是否正确。例如,当字符相等时,dp[i][j] = dp[i-1][j-1] + 1,否则取max(dp[i-1][j], dp[i][j-1])。这部分要用正确的数学表达式。 然后代码部分,Python的实现应该使用二维数组,初始化一个(m+1)x(n+1)的表格,然后填充,最后回溯。注意代码的缩进和语法正确性。可能需要用示例输入,比如"abcde"和"ace",输出长度3,子序列"ace"。 回溯部分需要解释如何从dp表回溯得到子序列,从右下角开始,根据状态转移的方向向上或向左移动,直到i或j为0。这个过程要详细但清晰。 最后的相关问题,可以包括时间空间优化,比如使用滚动数组减少空间复杂度,或者LCS的应用场景如生物信息学中的基因序列比对,版本控制系统中的差异比较等。还可以问如何输出所有可能的LCS,或者如何处理多个序列的LCS问题。 需要确保整个回答结构清晰,分步骤解释,代码示例正确,且引用正确标记。可能还需要检查是否有错别字或逻辑错误。</think>### 最长公共子序列(LCS)算法详解 #### 一、算法定义 最长公共子序列(Longest Common Subsequence, LCS)指在不改变元素相对顺序的前提下,找到两个序列共有的最长子序列。例如,序列$X=(\text{"abcde"})$和$Y=(\text{"ace"})$的LCS是$(\text{"ace"})$,长度为3。 #### 二、动态规划实现 ##### 1. 状态定义 构建二维数组$dp[m+1][n+1]$,其中$dp[i][j]$表示$X[0..i-1]$与$Y[0..j-1]$的LCS长度。 ##### 2. 状态转移方程 $$ dp[i][j] = \begin{cases} dp[i-1][j-1] + 1 & \text{if } X[i-1] = Y[j-1] \\ \max(dp[i-1][j], dp[i][j-1]) & \text{otherwise} \end{cases} $$ ##### 3. 边界条件 当$i=0$或$j=0$时,$dp[i][j] = 0$。 #### 三、Python代码实现 ```python def longest_common_subsequence(text1, text2): m, n = len(text1), len(text2) dp = [[0] * (n+1) for _ in range(m+1)] for i in range(1, m+1): for j in range(1, n+1): if text1[i-1] == text2[j-1]: dp[i][j] = dp[i-1][j-1] + 1 else: dp[i][j] = max(dp[i-1][j], dp[i][j-1]) # 回溯构造LCS lcs = [] i, j = m, n while i > 0 and j > 0: if text1[i-1] == text2[j-1]: lcs.append(text1[i-1]) i -= 1 j -= 1 elif dp[i-1][j] > dp[i][j-1]: i -= 1 else: j -= 1 return ''.join(reversed(lcs)), dp[m][n] ``` #### 四、算法分析 1. **时间复杂度**:$O(mn)$,双重循环遍历二维数组 2. **空间复杂度**:$O(mn)$,存储动态规划表[^3] 3. **回溯过程**:通过逆向追踪$dp$表,时间复杂度为$O(m+n)$ #### 五、应用场景 1. DNA序列比对(如基因相似度分析) 2. 文件差异比较(如git diff) 3. 语音识别中的模式匹配[^1] 相关问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值