动态规划之最长公共子序列(LCS)

本文深入探讨了最长公共子序列(LCS)问题,并通过动态规划的方法提供了高效解决方案。介绍了LCS的基本概念、特性及最优子结构,给出了具体实例与代码实现。

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

最长公共子序列是一个特别常见的动态规划问题,题目的意思是给定两个字符串X,Y,让你求一个字符串Z,使C中的每个字符即属于X,又属于Y,且字符在Z中的顺序必须和X,Y中的顺序是一样的,(这里区分子序列和子串的概念:子序列是不连续的,子串是连续的)举个例子X={A,B,C,B,D,A,B},Y={B.D.C.A.B.A},那么最长的公共序列Z={B,C,B,A}或{B,D,A,B},最朴素的算法就是遍历所有X的所有子序列,对于每个子序列检查是否是Y的子序列,这个方法太过暴力…光枚举X的子序列就要2^(X的长度),还要把每个字序列和Y的每个字序列比较,复杂度是O(2^(x+y的长度)),所以我们要找一个高效的算法。

首先我们分析这个问题的最优解是什么情况,令X={X1,X2,……,Xm},Y ={Y1,Y2,……,Yn},Z={Z1,Z2,……,Zk}为X和Y的任意LCS

1.如果Xm==Yn,那么Zk==Xm==Yn,且Zk-1是Xm-1和Yn-1的一个LCS
2.如果Xm!=Yn,那么Zk!=Xm意味着Zk是Xm-1和Yn的一个LCS
3.如果Xm!=Yn,那么Zk!=Yn,意味着Zk是Xm和Yn-1的一个LCS

所以两个序列的LCS包含两个序列的前缀的LCS,因此,LCS问题具有最优子结构的性质。而如果我们要求解X,Y的的LCS,我们就必须求解Xm-1和Yn-1的一个LCS,将Xm==Yn加入到这个LCS中,如果Xm!=Yn,那么我们就要求解Xm-1和Y的LCS的LCS或Yn-1和X的LCS,两个LCS取最长的,而这两个LCS的求解都需要求解Xm-1和Yn-1的LCS,所以我们要求解的问题具有重叠子问题的性质,所以这个问题符合动态规划的两个性质

我们这是就可以写出LCS为代码了
我们可以以Hdu的1159为例:

Common Subsequence

Problem Description
A subsequence of a given sequence is the given sequence with some elements (possible none) left out. Given a sequence X = < x1, x2, …, xm> another sequence Z = < z1, z2, …, zk> is a subsequence of X if there exists a strictly increasing sequence < i1, i2, …, ik> of indices of X such that for all j = 1,2,…,k, xij = zj. For example, Z = < a, b, f, c> is a subsequence of X = < a, b, c, f, b, c> with index sequence < 1, 2, 4, 6>. Given two sequences X and Y the problem is to find the length of the maximum-length common subsequence of X and Y.
The program input is from a text file. Each data set in the file contains two strings representing the given sequences. The sequences are separated by any number of white spaces. The input data are correct. For each set of data the program prints on the standard output the length of the maximum-length common subsequence from the beginning of a separate line.

Sample Input
abcfbc abfcab
programming contest
abcd mnp

Sample Output
4
2
0
题目就是标准的LCS问题,我们来看代码

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

int dp[1005][1005];
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    char s1[1000],s2[1000];
    while(cin>>s1+1>>s2+1) {
        memset(dp,0,sizeof(dp));
        int len1=strlen(s1),len2=strlen(s2),i,j,sum=0;
        for(i=1;i<len1;i++){
            for(j=1;j<len2;j++){
                if(s1[i]==s2[j]) {
                    dp[i][j]=dp[i-1][j-1]+1;
                }
                else {
                    dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        cout<<dp[i-1][j-1]<<endl;
    }
    return 0;
}

我们首先遍历S1的每一个元素,然后对每一个S2进行遍历,dp[i][j]中保存的就是到S1i和S2j的LCS,但我们看到我们每次都只用到dp[i]和dp[i-1]两行内容,这时我们可以在空间上进行优化,既然我们只用到这一行和上一行,那么我们只需要一个两行的数组,这里用到一个小技巧:^,1^0=1,1^1=0,这样对两行的选取进行优化,附上空间优化的代码:

#include<bits/stdc++.h>
using namespace std;
int dp[2][1005];
int main()
{
    char s1[1000],s2[1000];
    while(~scanf("%s%s",s1+1,s2+1)) {
        memset(dp,0,sizeof(dp));
        int len1=strlen(s1+1),len2=strlen(s2+1),i,j,flag=0;
        for(i=1;i<=len1;i++){
            for(j=1;j<=len2;j++){
                if(s1[i]==s2[j]) {
                    dp[flag][j]=dp[flag^1][j-1]+1;
                }
                else {
                    dp[flag][j]=max(dp[flag^1][j],dp[flag][j-1]);
                }
            }
            flag^=1;
        }
        cout<<dp[flag^1][j-1]<<endl;
    }
    return 0;
}

这时LCS的时间复杂度O(nm),空间复杂度O(4*max(n,m))已经有课非常大的优化,当然对于不同的问题可能还有其他剪枝的优化,这里就要看你的本事了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值