两种方法求解最长公共子序列问题并输出所有解

最长公共子序列(Longest Common Subsequence, LCS)是动态规划领域的经典问题,广泛应用于生物信息学(如DNA序列比对)、文本差异比对(如Git版本控制)等领域。本文将通过​​自顶向下递归+记忆化​​、​​自底向上动态规划​​以及​​回溯法找所有解​​三种方法,深入剖析LCS问题的求解过程,并提供完整的代码实现与实例验证

1.1 什么是LCS?

给定两个字符串text1和text2,其最长公共子序列定义为:在不改变字符相对顺序的前提下,两个字符串共有的最长字符序列。例如,text1=“abcde”,text2=“ace"的LCS为"ace”,长度为3。

1.2 动态规划的核心思想

动态规划通过​​分解问题​​、​​存储中间状态​​(即子问题的解)来避免重复计算。对于LCS问题,定义状态dp[i][j]表示text1前i个字符与text2前j个字符的LCS长度。状态转移方程如下:

在这里插入图片描述

边界条件为dp[0][j]=0和dp[i][0]=0,即空字符串的LCS长度为0。

算法实现与优化

2.1 自顶向下递归+记忆化(Top-Down)

通过递归分解问题,并用二维数组memo存储已计算的子问题结果,避免重复计算。

int upToDown(string& a, string& b, int m, int n, vector<vector<int>>& memo) {if (m == 0 || n == 0) return 0;if (memo[m][n] != -1) return memo[m][n]; // 记忆化查询if (a[m-1] == b[n-1]) {memo[m][n] = 1 + upToDown(a, b, m-1, n-1, memo);} else {memo[m][n] = max(upToDown(a, b, m-1, n, memo), upToDown(a, b, m, n-1, memo));}return memo[m][n];
}

时间复杂度​​:O(mn),​​空间复杂度​​:O(mn)。

2.2 自底向上动态规划(Bottom-Up)

通过迭代填充二维数组dp,从最小子问题逐步求解最终结果。

void downToUp(string a, string b) {int m = a.length(), n = b.length();vector<vector<int>> dp(m+1, vector<int>(n+1, 0));for (int i = 1; i <= m; ++i) {for (int j = 1; j <= n; ++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 << "LCS长度:" << dp[m][n];
}

优势​​:无需递归栈,适合大规模输入。

回溯法找所有最优解

3.1 回溯原理

基于动态规划表dp,从dp[m][n]反向追踪所有可能的路径。当字符相等时向左上回溯,否则根据dp值选择向上或向左回溯。

void LCS_Backtrack(string& X, string& Y, vector<vector<int>>& dp, int i, int j, string current, vector<string>& result) {if (i == 0 || j == 0) {reverse(current.begin(), current.end());result.push_back(current);return;}if (X[i-1] == Y[j-1]) {current.push_back(X[i-1]);LCS_Backtrack(X, Y, dp, i-1, j-1, current, result);current.pop_back();} else {if (dp[i-1][j] == dp[i][j]) {LCS_Backtrack(X, Y, dp, i-1, j, current, result);}if (dp[i][j-1] == dp[i][j]) {LCS_Backtrack(X, Y, dp, i, j-1, current, result);}}
}

注意​​:由于回溯路径是从后向前构建,最终需要反转字符串。

测试案例 && 完整代码

#include <bits/stdc++.h>
using namespace std;
const int N = 100;// 自底向上
void downToUp(string a, string b) {int al = a.length();int bl = b.length();int D[N][N];for (int i = 1; i <= al; i++) {for (int j = 1; j <= bl; j++) {if (a[i - 1] == b[j - 1]) {D[i][j] = D[i - 1][j - 1] + 1;} else {D[i][j] = max(D[i - 1][j], D[i][j - 1]);}}}cout << "最长公共子序列长度: " << D[al][bl] << endl;
}// 自上向下,传入的二维数组初始化为一
int upToDown(string& a, string& b, int m, int n, vector<vector<int>>& memo) {if (m == 0 || n == 0) return 0; // 递归终止条件if (memo[m][n] != -1) return memo[m][n]; // 计算过直接返回结果if (a[m - 1] == b[n - 1]) {memo[m][n] = 1 + upToDown(a, b, m - 1, n - 1, memo);} else {memo[m][n] = max(upToDown(a, b, m - 1, n, memo), upToDown(a, b, m, n - 1, memo));}return memo[m][n];
}// 3. 回溯法找到所有最长公共子序列
void LCS_Backtrack(string& X, string& Y, vector<vector<int>>& dp, int m, int n, string& current, vector<string>& result) {// 如果到达矩阵的边界,表示一个公共子序列的结束if (m == 0 || n == 0) {result.push_back(current);  // 到达边界,记录一个解return;}// 如果当前字符相等,将字符加入当前子序列,回溯到左上角if (X[m - 1] == Y[n - 1]) {current.push_back(X[m - 1]);  // 字符匹配,添加到当前子序列LCS_Backtrack(X, Y, dp, m - 1, n - 1, current, result);current.pop_back();  // 回溯,移除字符} else {// 如果上方 dp 值等于当前 dp 值,说明从上面来的if (dp[m - 1][n] == dp[m][n]) {LCS_Backtrack(X, Y, dp, m - 1, n, current, result);  // 向上回溯}// 如果左边 dp 值等于当前 dp 值,说明从左边来的if (dp[m][n - 1] == dp[m][n]) {LCS_Backtrack(X, Y, dp, m, n - 1, current, result);  // 向左回溯}}
}int main() {string a, b;cin >> a >> b;int m = a.length();int n = b.length();vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));for (int i = 1; i <= m; ++i) {for (int j = 1; j <= n; ++j) {if (a[i - 1] == b[j - 1]) {dp[i][j] = 1 + dp[i - 1][j - 1];  } else {dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);  }}}vector<string> result;string current;LCS_Backtrack(a, b, dp, m, n, current, result);cout << "所有的最长公共子序列: " << endl;for (const auto& seq : result) {string re = seq;reverse(re.begin(), re.end());cout << re << endl;}return 0;
}

输入
ABCBDAB
BDCABC
输出
4
所有的最长公共子序列:
BCAB
BDAB

end

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/79960.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot应急知识学习系统开发实现

概述 一个基于SpringBoot开发的应急知识学习系统&#xff0c;该系统提供了完整的用户注册、登录、知识学习与测评功能。对于开发者而言&#xff0c;这是一个值得参考的免费Java源码项目&#xff0c;可以帮助您快速构建类似的教育平台。 主要内容 5.2 注册模块的实现 系统采…

【Python 字符串】

Python 中的字符串&#xff08;str&#xff09;是用于处理文本数据的基础类型&#xff0c;具有不可变性、丰富的内置方法和灵活的操作方式。以下是 Python 字符串的核心知识点&#xff1a; 一、基础特性 定义方式&#xff1a; s1 单引号字符串 s2 "双引号字符串" s…

第十六届蓝桥杯大赛软件赛C/C++大学B组部分题解

第十六届蓝桥杯大赛软件赛C/C大学B组题解 试题A: 移动距离 问题描述 小明初始在二维平面的原点&#xff0c;他想前往坐标(233,666)。在移动过程中&#xff0c;他只能采用以下两种移动方式&#xff0c;并且这两种移动方式可以交替、不限次数地使用&#xff1a; 水平向右移动…

如何使用极狐GitLab 软件包仓库功能托管 npm?

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;关于中文参考文档和资料有&#xff1a; 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitLab 官网 软件包库中的 npm 包 (BASIC ALL) npm 是 JavaScript 和 Node.js 的默认包管理器。开发者使用 npm 共享和重用代码&#xff…

Matlab 基于Hough变换的人眼虹膜定位方法

1、内容简介 Matlab220-基于Hough变换的人眼虹膜定位方法 可以交流、咨询、答疑 2、内容说明 略 3、仿真分析 略 4、参考论文 略

chili调试笔记14 画线 页面布置 线条导出dxf

2025-05-08 09-05-06 llm画线 页面布置 expand有自己的格式 删了就会按照子元素格式 不加px无效 没有指定尺寸设置100%无效 怎么把线条导出dxf command({name: "file.export",display: "command.export",icon: "icon-export", }) export class…

蓝绿发布与金丝雀发布

蓝绿发布与金丝雀发布 一、蓝绿发布&#xff1a;像「搬家」一样安全上线1. 生活化故事2. 技术步骤拆解步骤①&#xff1a;初始状态步骤②&#xff1a;部署新版本到绿环境步骤③&#xff1a;内部验证绿环境步骤④&#xff1a;一键切换流量步骤⑤&#xff1a;监控与回滚 3. 蓝绿发…

【2025五一数学建模竞赛B题】 矿山数据处理问题|建模过程+完整代码论文全解全析

你是否在寻找数学建模比赛的突破点&#xff1f;数学建模进阶思路&#xff01; 作为经验丰富的美赛O奖、国赛国一的数学建模团队&#xff0c;我们将为你带来本次数学建模竞赛的全面解析。这个解决方案包不仅包括完整的代码实现&#xff0c;还有详尽的建模过程和解析&#xff0c…

JavaSE核心知识点02面向对象编程02-02(封装、继承、多态)

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 JavaSE核心知识点02面向对象编程02-02&#…

Yolo迁移训练-带训练源码

目录 下载Git 拉下yolo模型 下载labelimg 准备训练集 迁移训练 继续训练 下载Git Git - Downloading Package 拉下yolo模型 然后用克隆脚本拉下yolo模型 python clone_yolo.py import os import subprocess import sys import shutildef check_git_installed():"…

LangChain框架-PromptTemplate 详解

摘要 本文聚焦于 LangChain 框架中PromptTemplate提示词模板模块的深度解析,主要参考langchain_core.prompts源码模块与官方文档。系统梳理 LangChain 对提示词模板的封装逻辑与设计思路,旨在帮助读者构建全面、深入的知识体系,为高效运用LangChain 框架的提示词模板开发应用…

中小企业设备预测性维护三步构建法:从零到精的技术跃迁与中讯烛龙实践

在工业4.0浪潮中&#xff0c;中小企业常陷入"设备故障频发"与"数字化成本高企"的双重困境。本文基于半导体、食品加工等行业实证数据&#xff0c;结合中讯烛龙系统技术突破&#xff0c;为中小企业提供一套零基础、低门槛、可扩展的预测性维护实施框架&…

C30-函数

一 函数的优点 避免代码冗长模块化的设计思路(十分类似组装电脑)按功能划分,每个函数代表一个功能 二 函数的三要素 函数要先定义再使用(就像是变量一样)三要素: 函数名→体现功能参数列表 比如yf(x)→x就是参数又如yf(x,y)→x,y就是参数→参数的个数取决于需求 返回值:比如…

【Spring Boot 多模块项目】@MapperScan失效、MapperScannerConfigurer 报错终极解决方案

在使用 Spring Boot 构建多模块项目&#xff0c;集成 MyBatis-Plus 时&#xff0c;很多开发者会遇到类似如下启动报错&#xff1a; Error creating bean with name mapperScannerConfigurer ... Caused by: java.lang.IllegalArgumentException: Property basePackage is requ…

pimpl与unique_ptr的问题

PImpl与std::unique_ptr组合 pimpl(Pointer to Implementation)是C程序开发中非常常用的技巧之一&#xff0c;它的好处有&#xff1a; 节省程序编译时间保持程序/库的二进制兼容性隐藏实现细节 举例一个常见的pimpl的使用示例&#xff1a; // a.h class Impl; //前置声明 c…

C++类和对象:构造函数、析构函数、拷贝构造函数

引言 介绍&#xff1a;C类和对象&#xff1a;构造函数、析构函数、拷贝构造函数 _涂色_博主主页 C基础专栏 一、类的默认成员函数 先认识一下类中的默认成员函数&#xff1a; 默认成员函数就是用户没有显式实现&#xff0c;编译器会自动生成的成员函数称为默认成员函数。⼀个类…

CTF - PWN之ORW记录

CTF - Pwn之ORW记录https://mp.weixin.qq.com/s/uiRtqCSopn6U6NqyKJ8I7Q

RabbitMQ 中的六大工作模式介绍与使用

文章目录 简单队列&#xff08;Simple Queue&#xff09;模式配置类定义消费者定义发送消息测试消费 工作队列&#xff08;Work Queues&#xff09;模式配置类定义消费者定义发送消息测试消费负载均衡调优 发布/订阅&#xff08;Publish/Subscribe&#xff09;模式配置类定义消…

民宿管理系统6

普通管理员管理&#xff1a; 新增普通管理员&#xff1a; 前端效果&#xff1a; 前端代码&#xff1a; <body> <div class"layui-fluid"><div class"layui-row"><div class"layui-form"><div class"layui-f…

vue3+ant design vue + Sortable实现多级表格列拖动

1、最近遇到个需求&#xff0c;需使用vue3ant design vue Sortable实现多级表格的表头允许用户拖拽移动。即当用户拖拽一级表头时&#xff0c;其对应的子级表头及数据应同步移动&#xff0c;并且只允许一级非固定表头允许拖拽。 2、代码 <a-table:data-source"rowDat…