蓝桥杯+全国青少年信息素养大赛+电子学会PythonC/C++考级:最长公共子序列LCS解析

引言

在少儿编程的算法进阶路上,「动态规划」几乎是蓝桥杯软件赛、全国青少年信息素养大赛算法应用主题赛、以及中国电子学会 Python / C / C++ 考级的必考模块。而在序列型 DP 中,最长公共子序列(Longest Common Subsequence,LCS)又是最经典、也最容易与「最长公共子串」混淆的一题。

很多同学背过状态转移方程,却在比赛里因为下标偏移、初始化、子序列与子串分不清而丢分。今天我们用一道原创练习题,把 LCS 的状态定义、转移、方案还原、复杂度与易错点一次性讲透,并给出 C++ / Python 双语言实现。

题目:校园诗词共背挑战(原创练习题)

题目描述

校园读书节举办「诗词共背」活动。老师把两首课外诗各摘取一段,转写成由大写字母组成的字符序列 A 和 B(长度均不超过 1000)。请你求出这两个序列的最长公共子序列的长度,并还原出一种公共子序列方案。

  • 子序列不要求连续,只要相对顺序一致即可;
  • 若需要输出「字典序最小」的方案,请在还原时优先走字典序小的方向(本文先给出任意一种方案)。

输入样例

复制代码
A = ABCBDAB
B = BDCABA

输出

复制代码
长度:4
一种公共子序列:BCBA

考点映射

  • 蓝桥杯(第十八届软件赛 C/C++、Python 程序设计报名 2026-10-08---12-18,省赛 2027-04):字符串 DP 基础题型,常作为填空题或大题第一问。
  • 全国青少年信息素养大赛·算法应用主题赛:动态规划考点大纲明确包含序列型 DP 与字符串处理。
  • 中国电子学会 Python / C / C++ 考级(12 月场次报名进行中):二级 / 三级的 DP 与字符串综合应用。

核心考点拆解

  1. 子序列 vs 子串:子序列不要求连续,子串必须连续------这是 LCS 与「最长公共子串」的本质区别。
  2. 状态定义 :dp[i][j] 表示序列 A 的前 i 个字符与序列 B 的前 j 个字符的 LCS 长度。
  3. 状态转移:
  4. 若 A[i-1] == B[j-1],则这两个字符可以同时纳入,dp[i][j] = dp[i-1][j-1] + 1;
  5. 否则只能「舍弃 A 的末位」或「舍弃 B 的末位」中取较大者,dp[i][j] = max(dp[i-1][j], dp[i][j-1])。
  6. 边界初始化 :dp[0][*] 与 dp[*][0] 全部为 0(空序列与任何序列的 LCS 长度为 0)。
  7. 复杂度:时间 O(n·m),空间 O(n·m),可滚动压缩到 O(min(n, m))。
  8. 方案还原 :从 dp[n][m] 沿转移路径倒推,相等则收字符、否则走向值更大的上一格。

解法一:二维 DP 求长度(C++ / Python 双版)

C++ 实现

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

int main() {
    string A = "ABCBDAB", B = "BDCABA";
    int n = A.size(), m = B.size();
    vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (A[i - 1] == B[j - 1])
                dp[i][j] = dp[i - 1][j - 1] + 1;
            else
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
        }
    }
    cout << dp[n][m] << "\n"; // 输出 4
    return 0;
}

Python 实现

python 复制代码
def lcs_len(A: str, B: str) -> int:
    n, m = len(A), len(B)
    dp = [[0] * (m + 1) for _ in range(n + 1)]
    for i in range(1, n + 1):
        for j in range(1, m + 1):
            if A[i - 1] == B[j - 1]:
                dp[i][j] = dp[i - 1][j - 1] + 1
            else:
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
    return dp[n][m]

print(lcs_len("ABCBDAB", "BDCABA"))  # 输出 4

解法二:回溯还原一种方案

光求长度不够,很多赛题要求「输出具体方案」。我们在求完 dp 后,从右下角 (n, m) 倒推:相等就把该字符收进结果并同时向左上走;不相等则走向值更大的相邻格。

C++ 实现

cpp 复制代码
string restore(const string& A, const string& B, const vector<vector<int>>& dp) {
    string res;
    int i = A.size(), j = B.size();
    while (i > 0 && j > 0) {
        if (A[i - 1] == B[j - 1]) { res += A[i - 1]; i--; j--; }
        else if (dp[i - 1][j] >= dp[i][j - 1]) i--;
        else j--;
    }
    reverse(res.begin(), res.end());
    return res;
}

Python 实现

python 复制代码
def lcs_seq(A: str, B: str) -> str:
    n, m = len(A), len(B)
    dp = [[0] * (m + 1) for _ in range(n + 1)]
    for i in range(1, n + 1):
        for j in range(1, m + 1):
            if A[i - 1] == B[j - 1]:
                dp[i][j] = dp[i - 1][j - 1] + 1
            else:
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
    res = []
    i, j = n, m
    while i > 0 and j > 0:
        if A[i - 1] == B[j - 1]:
            res.append(A[i - 1]); i -= 1; j -= 1
        elif dp[i - 1][j] >= dp[i][j - 1]:
            i -= 1
        else:
            j -= 1
    return ''.join(reversed(res))

print(lcs_seq("ABCBDAB", "BDCABA"))  # 输出 BCBA

时间 / 空间复杂度

  • 时间复杂度:O(n·m),两层循环各扫一遍,蓝桥杯与信息素养大赛数据范围(n, m ≤ 1000)完全可在 1 秒内跑完。
  • 空间复杂度 :基础写法 O(n·m);由于 dp[i] 只依赖 dp[i-1],可压缩为两行,降到 O(min(n, m))。
  • 方案还原:回溯一趟 O(n + m)。

易错点提醒

  1. 下标偏移 :dp[i][j] 对应的是 A[0..i-1]、B[0..j-1],比较字符务必用 A[i-1]、B[j-1],直接写 A[i] 会越界或错位。
  2. 子序列 ≠ 子串 :不相等时取 max(dp[i-1][j], dp[i][j-1]) 表示「二选一舍弃末位」,不要只和末尾比,那是子串的做法。
  3. 初始化漏写:第 0 行、第 0 列必须全 0,否则所有值会整体偏移 1。
  4. 还原方向 :回溯时 i、j 从 n、m 开始,只有字符相等才同时 i--, j-- 并收字符;走错一格方案就错。
  5. 多方案要求:题目若要求「字典序最小 / 所有方案」,需额外处理(见进阶),否则任意一条合法方案即可。
  6. 数据范围 :只求长度用 int 足够;若求「方案数」要 long long 或取模 1000000007,避免溢出。

进阶

  1. 输出所有 LCS:另开一个计数 DP 记录到达每个格的方案总数,再用记忆化 DFS 枚举全部路径。
  2. 两排列的 LCS → LIS:当 A、B 是同一集合的排列时,把 A 中元素映射到它在 B 中的下标序列,该序列的最长上升子序列长度即为 LCS 长度,复杂度降到 O(n log n)。
  3. 与编辑距离的关系 :只允许「增 / 删」两种操作时,编辑距离 = n + m - 2 × LCS,可互相转化。
  4. 空间优化 :dp 只保留上一行,用一维数组从后往前更新,空间降到 O(min(n, m))。
  5. 连续版本 :把「子序列」改成「子串」,则 dp[i][j] 只在 A[i-1]==B[j-1] 时 = dp[i-1][j-1]+1,否则置 0,并随时记录最大值,即最长公共子串。
  6. 蓝桥杯变式:在求 LCS 长度的基础上,输出「方案数对 1000000007 取模」,综合考察 DP + 取模,是省赛常见加强。

小结与互动

最长公共子序列是序列型 DP 的「基本功」:记住 dp[i][j] 的语义、相等加一否则取两侧最大、下标用 i-1/j-1、空串初始化为 0,再配上回溯还原,就能稳稳拿下蓝桥杯、信息素养大赛与电子学会考级里的这类题。

你在备赛时还被哪些动态规划题卡过?欢迎在评论区聊聊,我会挑高频问题在后续文章拆解。也欢迎关注,每天一篇少儿编程算法精解,陪你一路打怪升级。


📚 免费少儿编程资料(夸克网盘领取)

以下资料来自夸克网盘分享,点击链接可直接保存;若需在 App 内打开,也可复制下方明文链接:

  1. 全国青少年信息素养大赛复赛集训题目Python&C++.docx
    https://pan.quark.cn/s/93995d3cb150
  2. 2024信息素养-智能算法应用挑战赛-复赛初中组题目7月7日.pdf
    https://pan.quark.cn/s/da97b5dbf75d
  3. Python背记手册.pdf
    https://pan.quark.cn/s/7568ae9ca92b
  4. Python课程
    https://pan.quark.cn/s/a94bf02d00c6
  5. 2024信息素养大赛图形化复赛集训题答案3-9
    https://pan.quark.cn/s/6ccab7ec3cbc
  6. 2025年03月份电子学会考级真题
    https://pan.quark.cn/s/4403c4228912
  7. 2025全国青少年信息素养大赛赛项说明
    https://pan.quark.cn/s/d9d0df4a9f29
  8. 青少儿信息素养大赛编程资料
    https://pan.quark.cn/s/4ab6bd83be8a

资料持续更新,关注获取最新分享。

相关推荐
Y3815326622 小时前
印度电商数据采集教程:用 SerpBase 查 Flipkart/Myntra/Ajio 商品(附代码)
数据库·python
阿维的博客日记2 小时前
大白话解析快排Python
python
doujiaozh3 小时前
我在 DOS 6.22 上跑通了 Kimi AI:一场跨越 40 年的硬核接力
python
凛铄linshuo3 小时前
分析业务流程什么时候适合断言?
python·pytest
倚楼听风语3 小时前
Kotlin 协程与 suspend 原理详解
java·算法·kotlin
程序员清风3 小时前
双指针与滑动窗口:高效处理连续区间
开发语言·算法
import_random3 小时前
[python]IDE可以自动检测venv工具创建的虚拟环境.venv吗
python
代钦塔拉3 小时前
【C++/Qt 进阶】设计模式系列(2):工厂模式——从游戏角色到披萨店
c++·算法·设计模式
思茂信息3 小时前
电磁仿真学涉及哪些算法?
人工智能·算法·数学建模·matlab·emc·电磁仿真·仿真辐射