例如,X=“ABCBDAB”,Y=“BCDB”是X的一个子序列。给定两个序列A和B,称序列Z是A和B的公共子序列,是指Z同是A和B的子序列。编写算法求一直两序列A和B的最长公共子序列。

本文详细解析了计算两个字符串最长公共子序列的方法,包括递归法和动态规划法,并通过实例展示了如何实现和优化算法。文章还提供了解码最长公共子序列的步骤,帮助读者理解算法的具体应用。

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

分析:
v[i][j]存储序列“a0,a1,…,ai-1”, “b0,b1,…,bj-1”的最长公共子序列的长度。
则v[i][j]的计算如下:
(1). v[i][j] = 0;
(2). v[i][j] = v[i-1][j-1]+1, 如果a[i-1] = b[j-1]
(3). v[i][j] = max(v[i-1][j],v[i][j-1]),如果a[i-1] != b[j-1]

C++代码:

解法一(递归法):

#include <iostream>
#include <vector>
#include <string>
using namespace std;

//在计算最优值,
int lcs_len(const string &s1,const string &s2, vector<vector<int>> &v, int i, int j)
{
    if (i == 0 || j == 0)
    {
        v[i][j] = 0;
    }
    else
    {
        //不保证会求v中的每个元素
        if (s1[i-1] == s2[j-1])
        {
            v[i][j] = lcs_len(s1,s2,v,i-1,j-1) + 1;
        }
        else 
        {
            int L1 = lcs_len(s1,s2,v,i-1,j);
            int L2 = lcs_len(s1,s2,v,i,j-1);
            if (L1 >= L2)
            {
                v[i][j] = L1;
            }
            else
                v[i][j] = L2;
        }
    }
    return v[i][j];
}

//构造最长公共子序列,k为公共子序列长度
void buile_lcs(const string &s1,const string &s2,const vector<vector<int>> &v, int i, int j, string &sub/*, int k*/)
{
    if (i == 0 || j == 0)
    {
        return ;
    }
    else
    {

        if (v[i][j] == v[i][j-1])
        {
            buile_lcs(s1,s2,v,i,j-1,sub);
        } 
        else if (v[i][j] == v[i-1][j])
        {
            buile_lcs(s1,s2,v,i-1,j,sub);
        }
        else
        {
            sub = s1[i-1] + sub;
            buile_lcs(s1,s2,v,i-1,j-1,sub);
        }
    }
}

int main()
{
    string s1,s2;
    cin>>s1>>s2;
    int i = s1.length();
    int j = s2.length();
    vector<vector<int>> v;
    vector<int> tmp(j+1,0);
    for (int k=0;k<=i;++k)
    {
        v.push_back(tmp);
    }
    lcs_len(s1,s2,v,i,j);
    string sub = "";
    buile_lcs(s1,s2,v,i,j,sub);
    /*for (vector<vector<int>>::iterator ita=v.begin();ita != v.end();++ita)
    {
        for (vector<int>::iterator itb=ita->begin();itb!= ita->end();++itb)
        {
            cout<<*itb<<' ';
        }
        cout<<endl;
    }*/
    cout<<sub<<endl;
    return 0;
}

解法二:

#include <iostream>
#include <vector>
#include <string>
using namespace std;

//在计算最优值
void lcs_len(const string &s1,const string &s2, vector<vector<int>> &v)
{
    int len1 = s1.length();
    int len2 = s2.length();
    vector<int> tmp(len2+1,0);
//  v.resize(len1+1);
    for (int i=0;i<len1+1;++i)
    {
        v.push_back(tmp);
    }
    /*cout<<v.size()<<endl;*/
    for (int i=1;i<len1+1;++i)
    {
        for (int j=1;j<len2+1;++j)
        {
            if (s1[i-1] == s2[j-1])
            {
                v[i][j] = 1+v[i-1][j-1];
            }
            else if (v[i-1][j] >= v[i][j-1])
            {
                v[i][j] = v[i-1][j];
            } 
            else
            {
                v[i][j] = v[i][j-1];
            }
        }
    }
}

//构造最长公共子序列,k为公共子序列长度
string buile_lcs(const string &s1,const string &s2,const vector<vector<int>> &v)
{
    string com_subseq="";
    int len1 = s1.length();
    int len2 = s2.length();
    int i = len1;
    int j = len2;
    int k = v[len1][len2];
    while (k)
    {
        if (v[i][j] == v[i][j-1])
        {
            --j;
        }
        else if (v[i][j] == v[i-1][j])
        {
            --i;
        }
        else
        {
            com_subseq = s1[i-1]+com_subseq;
            --i;
            --j;
            --k;
        }
    }
    return com_subseq;
}

int main()
{
    string s1,s2;
    cin>>s1>>s2;
    vector<vector<int>> v;
    lcs_len(s1, s2,v);
    /*for (vector<vector<int>>::iterator ita = v.begin();ita != v.end();++ita)
    {
        for (vector<int>::iterator itb = ita->begin();itb != ita->end();++itb)
        {
            cout<<*itb<<' ';
        }
        cout<<endl;
    }
    cout<<endl;*/
    cout<<buile_lcs(s1,s2,v);
    return 0;
}

运行结果:
输入:ABCBDAB BDCABA
输出:BDAB

### 动态规划最长公共子序列(LCS) #### 问题定义 给定两个字符串 \( X = \text{"ABCBDAB"} \) \( Y = \text{"BDCABA"} \),目标是找到它们的最长公共子序列(LCS),即在保持字符顺序不变的情况下,存在于者中的最长子序列。 #### 解决方法 动态规划是一种有效的解决方案。其核心思想是利用子问题的结果来构建更大的问题解答。对于这个问题,可以定义状态转移方程如下: 设 \( dp[i][j] \) 表示字符串 \( X \) 的前 \( i \) 个字符字符串 \( Y \) 的前 \( j \) 个字符之间的最长公共子序列长度,则有以下递推关系[^2]: \[ dp[i][j] = \begin{cases} 0, & \text{如果 } i = 0 \text{ 或者 } j = 0 \\ dp[i-1][j-1] + 1, & \text{如果 } X[i-1] = Y[j-1] \\ \max(dp[i-1][j], dp[i][j-1]), & \text{如果 } X[i-1] \neq Y[j-1] \end{cases} \] 其中: - 当 \( X[i-1] = Y[j-1] \) 时,说明当前字符匹配,因此可以在之前的状态基础上增加一个字符; - 否则取删除其中一个字符后的最大值作为新的状态值。 最终结果存储于 \( dp[m][n] \),其中 \( m \) \( n \) 分别表示字符串 \( X \) \( Y \) 的长度。 以下是基于此逻辑的一个 Java 实现代码片段[^2]: ```java public class LCS { public static void main(String[] args) { Scanner sc = new Scanner(System.in); String a = sc.nextLine(); String b = sc.nextLine(); System.out.println(lcs(a, b)); sc.close(); } private static int lcs(String s1, String s2) { int m = s1.length(); int n = s2.length(); int[][] dp = new int[m + 1][n + 1]; for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { if (s1.charAt(i - 1) == s2.charAt(j - 1)) { dp[i][j] = dp[i - 1][j - 1] + 1; } else { dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); } } } return dp[m][n]; } } ``` 该程序读入行输入分别代表字符串 \( X \) \( Y \),并打印出它们的最长公共子序列长度。 为了进一步获取实际的最长公共子序列而非仅限于长度,在完成上述表格填充后还需要回溯路径以重建具体序列[^5]。这通常涉及从右下角向左上方遍历二维数组,并记录每次导致数值变化的位置对应的字符。 #### 结果解释 按照以上算法处理样本数据 \( X = \text{"ABCBDAB"} \), \( Y = \text{"BDCABA"} \): | | "" | B | D | C | A | B | A | |-------|----|----|----|----|----|----|----| | **""**| 0 | 0 | 0 | 0 | 0 | 0 | 0 | | **A** | 0 | 0 | 0 | 0 | 1 | 1 | 1 | | **B** | 0 | 1 | 1 | 1 | 1 | 2 | 2 | | **C** | 0 | 1 | 1 | 2 | 2 | 2 | 2 | | **B** | 0 | 1 | 1 | 2 | 2 | 3 | 3 | | **D** | 0 | 1 | 2 | 2 | 2 | 3 | 3 | | **A** | 0 | 1 | 2 | 2 | 3 | 3 | 4 | | **B** | 0 | 1 | 2 | 2 | 3 | 4 | 4 | 由此得出最长公共子序列为 BCBA,长度为 4。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值