当前位置: 首页 > news >正文

最长公共子序列(LCS)——从零开始的动态规划

LCS最长公共子序列:从理解到实现,一次讲透

在字符串和动态规划的学习中,LCS(Longest Common Subsequence,最长公共子序列)是一个绕不开的经典问题。很多人一开始觉得它和“最长公共子串”差不多,但真正做题的时候才发现完全不是一回事。

这篇文章就从最基础的概念出发,把LCS的思路、状态转移、代码实现以及一些常见误区讲清楚。


什么是LCS;先别急着写代码

给定两个字符串:

s1 = ABCBDAB s2 = BDCABA

我们要找的是:

在不改变相对顺序的前提下,两个字符串的“最长公共子序列”

注意两个关键词:

  • 子序列:可以不连续

  • 顺序不能变

例如:

BCBA

就是一个合法答案。


子序列 vs 子串;别再混了

这是最常见的坑。

  • 子序列:可以跳着选

  • 子串:必须连续

举个例子:

ABCDEF
  • 子序列可以是:ACE

  • 子串只能是:ABC、BCD、DEF 这种连续的

一句话记住:

LCS可以“跳”,最长公共子串不能跳


为什么LCS适合用动态规划

如果用暴力去做:

  • 每个字符串都有 2^n 种子序列

  • 两个字符串组合起来,复杂度直接爆炸

但LCS有一个很明显的特征:

大问题可以拆成小问题

这正是动态规划的典型应用场景。


核心思路;状态是怎么定义的

我们定义:

dp[i][j] = s1前i个字符 和 s2前j个字符 的LCS长度

也就是说:

  • i 控制 s1

  • j 控制 s2

最终答案就是:

dp[n][m]

状态转移;真正关键的地方

分两种情况:

1. 当前字符相等

if (s1[i-1] == s2[j-1]) dp[i][j] = dp[i-1][j-1] + 1;

理解:

既然两个字符一样,那就可以加入公共子序列


2. 当前字符不相等

dp[i][j] = max(dp[i-1][j], dp[i][j-1]);

理解:

  • 要么丢掉 s1 的当前字符

  • 要么丢掉 s2 的当前字符

取更优的那种情况。


完整代码实现;最标准写法

#include <iostream> #include <vector> using namespace std; int main() { string s1 = "ABCBDAB"; string s2 = "BDCABA"; int n = s1.size(); int m = s2.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 (s1[i - 1] == s2[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 << "LCS长度: " << dp[n][m] << endl; return 0; }

如果不只是长度;还想输出序列

很多题目不仅要求长度,还要输出具体的LCS。

思路是:

从 dp[n][m] 反向回溯

大致逻辑:

  • 如果字符相等 → 加入答案,往左上走

  • 否则 → 往较大的方向走

示例代码:

string res = ""; int i = n, j = m; while (i > 0 && j > 0) { if (s1[i - 1] == s2[j - 1]) { res = s1[i - 1] + res; i--; j--; } else if (dp[i - 1][j] > dp[i][j - 1]) { i--; } else { j--; } } cout << "LCS: " << res << endl;

空间优化;从二维到一维

观察状态转移:

dp[i][j] 只依赖: dp[i-1][j], dp[i][j-1], dp[i-1][j-1]

可以优化成一维数组:

vector<int> dp(m + 1, 0); for (int i = 1; i <= n; i++) { int prev = 0; for (int j = 1; j <= m; j++) { int temp = dp[j]; if (s1[i - 1] == s2[j - 1]) { dp[j] = prev + 1; } else { dp[j] = max(dp[j], dp[j - 1]); } prev = temp; } }

常见应用场景

LCS不仅是模板题,还经常出现在:

  • 判断字符串相似度

  • 编辑距离的变形问题

  • DNA序列分析

  • diff工具(代码对比)


一些容易踩的坑

  • 把子序列当成子串

  • dp数组下标错位(i-1 / j-1)

  • 忘记初始化为0

  • 回溯时路径走错


总结

LCS其实可以用一句话总结:

两个字符串在“允许跳过字符”的情况下,最长能匹配多少

核心就三点:

  • 状态定义:dp[i][j]

  • 状态转移:相等 +1,不等取max

  • 回溯输出(可选)


最后

如果你刚学动态规划,LCS是一个非常好的练习题:

  • 不复杂

  • 结构清晰

  • 适合反复练

建议你自己手推一遍 dp 表,再写代码,会比直接背模板收获大得多。

http://www.jsqmd.com/news/519361/

相关文章:

  • 学习web第三天
  • 深入解析DRAM:从基础原理到现代应用
  • Hive实战:3种生成自增ID的保姆级教程(附row_number与UDF对比)
  • 《医学大数据与人工智能》第二周
  • 计算机毕业设计:基于Python的图书数据分析系统 Flask框架 可视化 爬虫 书籍 大数据 机器学习(建议收藏)✅
  • C++中的 lower_bound 和 upper_bound:一篇讲清楚
  • 基于FPGA的FOC电流环手动编写Verilog实现:高效、可读性强的源码与Simulink模...
  • 迷宫算法面试通关指南:华为真题详解+DFS/BFS最优解套路
  • SpringBoot实战:5分钟搞定SSE消息推送,告别轮询烦恼
  • 告别人工规则:用MAPPO+自适应环境生成器,手把手教你训练能应对未知障碍物的无人机协同追捕AI
  • 从摄像头到CAN总线:手把手梳理智驾域控制器的数据接口与布线实战
  • 2026年 上海苏州OPC园区租赁招商推荐榜:精选优质产业园区,解析区位优势与服务体系,助力企业高效选址 - 品牌企业推荐师(官方)
  • LangGraph实战:构建具备状态与决策能力的智能体工作流
  • 计算机毕业设计:Python豆瓣图书数据分析平台 Flask框架 可视化 爬虫 书籍 大数据 机器学习(建议收藏)✅
  • 保姆级教程:用trackeval评估dancetrack多目标跟踪结果(附完整文件结构解析)
  • Codeforces Round 2209
  • UI 界面组成,控制界面代码
  • 【面试真题拆解】Java的Static关键字到底怎么用?
  • 3月18日笔记
  • Cookie操作避坑指南:从浏览器复制到Python requests的完整流程解析
  • 保姆级教程:用OpenWRT打造企业级访客WiFi(含防火墙规则+DHCP避坑指南)
  • Xilinx MMCM动态相位调整:从原理到实战的时钟微调指南
  • 信息学奥赛必备:5分钟搞定配对碱基链的两种C++解法(附完整代码)
  • 从PID到深度学习:柔性机器人控制算法演进全解析(附Python示例代码)
  • 从键盘到显示屏:给STM32F4计算器加个OLED界面(I2C驱动教程)
  • 揭示提示工程架构师创新实验室的神秘面纱
  • PyQt5桌面应用内嵌Web地图避坑指南:从QWebEngineView加载到JS交互全流程
  • 华为OceanStor存储管理员密码遗忘?一文详解从串口到Web的完整重置路径
  • Pixel 2XL刷机指南:从AOSP源码编译到烧录的完整流程(附常见错误解决)
  • 基于PLC的煤矿皮带运输机控制系统 plc煤矿皮带运输机采用西门子博途s7-1200编程