引言
在少儿编程的算法进阶路上,「动态规划」几乎是蓝桥杯软件赛、全国青少年信息素养大赛算法应用主题赛、以及中国电子学会 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 与字符串综合应用。
核心考点拆解
- 子序列 vs 子串:子序列不要求连续,子串必须连续------这是 LCS 与「最长公共子串」的本质区别。
- 状态定义 :
dp[i][j]表示序列 A 的前i个字符与序列 B 的前j个字符的 LCS 长度。 - 状态转移:
- 若
A[i-1] == B[j-1],则这两个字符可以同时纳入,dp[i][j] = dp[i-1][j-1] + 1; - 否则只能「舍弃 A 的末位」或「舍弃 B 的末位」中取较大者,
dp[i][j] = max(dp[i-1][j], dp[i][j-1])。 - 边界初始化 :
dp[0][*]与dp[*][0]全部为 0(空序列与任何序列的 LCS 长度为 0)。 - 复杂度:时间 O(n·m),空间 O(n·m),可滚动压缩到 O(min(n, m))。
- 方案还原 :从
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)。
易错点提醒
- 下标偏移 :
dp[i][j]对应的是A[0..i-1]、B[0..j-1],比较字符务必用A[i-1]、B[j-1],直接写A[i]会越界或错位。 - 子序列 ≠ 子串 :不相等时取
max(dp[i-1][j], dp[i][j-1])表示「二选一舍弃末位」,不要只和末尾比,那是子串的做法。 - 初始化漏写:第 0 行、第 0 列必须全 0,否则所有值会整体偏移 1。
- 还原方向 :回溯时
i、j从n、m开始,只有字符相等才同时i--, j--并收字符;走错一格方案就错。 - 多方案要求:题目若要求「字典序最小 / 所有方案」,需额外处理(见进阶),否则任意一条合法方案即可。
- 数据范围 :只求长度用
int足够;若求「方案数」要long long或取模 1000000007,避免溢出。
进阶
- 输出所有 LCS:另开一个计数 DP 记录到达每个格的方案总数,再用记忆化 DFS 枚举全部路径。
- 两排列的 LCS → LIS:当 A、B 是同一集合的排列时,把 A 中元素映射到它在 B 中的下标序列,该序列的最长上升子序列长度即为 LCS 长度,复杂度降到 O(n log n)。
- 与编辑距离的关系 :只允许「增 / 删」两种操作时,编辑距离 =
n + m - 2 × LCS,可互相转化。 - 空间优化 :
dp只保留上一行,用一维数组从后往前更新,空间降到 O(min(n, m))。 - 连续版本 :把「子序列」改成「子串」,则
dp[i][j]只在A[i-1]==B[j-1]时= dp[i-1][j-1]+1,否则置 0,并随时记录最大值,即最长公共子串。 - 蓝桥杯变式:在求 LCS 长度的基础上,输出「方案数对 1000000007 取模」,综合考察 DP + 取模,是省赛常见加强。
小结与互动
最长公共子序列是序列型 DP 的「基本功」:记住 dp[i][j] 的语义、相等加一否则取两侧最大、下标用 i-1/j-1、空串初始化为 0,再配上回溯还原,就能稳稳拿下蓝桥杯、信息素养大赛与电子学会考级里的这类题。
你在备赛时还被哪些动态规划题卡过?欢迎在评论区聊聊,我会挑高频问题在后续文章拆解。也欢迎关注,每天一篇少儿编程算法精解,陪你一路打怪升级。
📚 免费少儿编程资料(夸克网盘领取)
以下资料来自夸克网盘分享,点击链接可直接保存;若需在 App 内打开,也可复制下方明文链接:
- 全国青少年信息素养大赛复赛集训题目Python&C++.docx
https://pan.quark.cn/s/93995d3cb150 - 2024信息素养-智能算法应用挑战赛-复赛初中组题目7月7日.pdf
https://pan.quark.cn/s/da97b5dbf75d - Python背记手册.pdf
https://pan.quark.cn/s/7568ae9ca92b - Python课程
https://pan.quark.cn/s/a94bf02d00c6 - 2024信息素养大赛图形化复赛集训题答案3-9
https://pan.quark.cn/s/6ccab7ec3cbc - 2025年03月份电子学会考级真题
https://pan.quark.cn/s/4403c4228912 - 2025全国青少年信息素养大赛赛项说明
https://pan.quark.cn/s/d9d0df4a9f29 - 青少儿信息素养大赛编程资料
https://pan.quark.cn/s/4ab6bd83be8a
资料持续更新,关注获取最新分享。