【动态规划】最长公共子序列模板

最长公共子序列

题目传送门:AcWing 897. 最长公共子序列

一、题目描述

给定两个长度分别为 NM 的字符串 AB,求既是 A 的子序列又是 B 的子序列的字符串的最长长度。

输入格式

  • 第一行包含两个整数 NM
  • 第二行包含一个长度为 N 的字符串 A
  • 第三行包含一个长度为 M 的字符串 B

输出格式

  • 输出一个整数,表示最长公共子序列的长度。

数据范围
1 ≤ N, M ≤ 1000

输入样例

4 5
acbd
abedc

输出样例

3

二、题目分析

我们需要找到两个字符串 AB 的最长公共子序列(LCS)。子序列不要求连续,但必须保持相对顺序。

三、问题思考

算法分析

  • 暴力解法:枚举 A 的所有子序列,检查是否在 B 中出现,时间复杂度为 O(2^N × M),不可行。
  • 动态规划(DP):利用二维 DP 数组存储中间状态,避免重复计算。

前置知识

  • 子序列:从原序列中删除若干元素后剩余元素的序列,不要求连续。
  • 动态规划:通过状态转移方程递推求解最优解。

四、动态规划思路

a. 状态表示

定义 f[i][j] 表示 A 的前 i 个字符和 B 的前 j 个字符的最长公共子序列长度。

b. 初始化

  • f[0][j] = 0A 为空串时,LCS 长度为 0)
  • f[i][0] = 0B 为空串时,LCS 长度为 0)

c. 状态转移推导

  • 如果 A[i] == B[j],则 f[i][j] = f[i-1][j-1] + 1(当前字符可以加入 LCS)。
  • 否则,f[i][j] = max(f[i-1][j], f[i][j-1])(取前一步的最优解)。

d. 最终结果

f[n][m] 即为 AB 的最长公共子序列长度。

五、代码实现

#include 
using namespace std;

const int N = 1010;
int n, m;
char a[N], b[N];
int f[N][N];  // f[i][j] 表示 A[1~i] 和 B[1~j] 的最长公共子序列长度

int main() {
    // 递推式出现 f[i-1][j-1],如果 i,j 从 0 开始会出现负值,所以下标从 1 开始
    cin >> n >> m >> a + 1 >> b + 1;

    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) {
            f[i][j] = max(f[i - 1][j], f[i][j - 1]);  // 不选 A[i] 或不选 B[j]
            if (a[i] == b[j])  // 当前字符匹配,可以增加 LCS 长度
                f[i][j] = f[i - 1][j - 1] + 1;
        }

    printf("%d", f[n][m]);
    return 0;
}

六、重点细节

  1. 下标从 1 开始

    • 由于状态转移涉及 f[i-1][j-1],如果 ij 从 0 开始会导致越界,因此输入字符串时从 a+1b+1 开始存储。
  2. 状态转移方程

    • f[i][j] = max(f[i-1][j], f[i][j-1]) 表示不选 A[i] 或不选 B[j] 时的最优解。
    • A[i] == B[j] 时,直接继承 f[i-1][j-1] 并 +1。
  3. 初始化

    • f[0][j]f[i][0] 默认为 0,无需显式初始化。

七、复杂度分析

  • 时间复杂度:O(N × M),双重循环遍历整个 DP 表。
  • 空间复杂度:O(N × M),使用二维数组存储 DP 状态。

八、总结

本题是经典的动态规划问题,关键在于:

  1. 定义 f[i][j] 表示 A[1~i]B[1~j] 的 LCS 长度。
  2. 分情况讨论状态转移:
    • 字符匹配时,直接继承左上角状态 +1。
    • 不匹配时,取左边或上边的最大值。
  3. 最终结果存储在 f[n][m] 中。

熟练掌握 DP 的状态表示和转移方程是解决此类问题的关键!

你可能感兴趣的:(Acwing算法课学习笔记记录,#,算法题解-动态规划,动态规划,算法,Acwing,c++,蓝桥杯)