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

news2025/7/14 2:02:23

最长公共子序列(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.coloradmin.cn/o/2372042.html

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

相关文章

【Linux网络】网络协议基础

网络基础 计算机网络背景 独立模式:计算机之间相互独立 网络互联:多台计算机连接在一起,完成数据共享 局域网LAN:计算机数量更多了,通过交换机和路由器连接在一起 广域网WAN:将远隔千里的计算机都连在一起 所谓"局域网"和"广域网"只是一个相对的概念.比…

LeapVAD:通过认知感知和 Dual-Process 思维实现自动驾驶飞跃——论文阅读

《LeapVAD: A Leap in Autonomous Driving via Cognitive Perception and Dual-Process Thinking》2025年1月发表&#xff0c;来自浙江大学、上海AI实验室、慕尼黑工大、同济大学和中科大的论文。 尽管自动驾驶技术取得了显著进步&#xff0c;但由于推理能力有限&#xff0c;数…

windows 部署 Kafka3.x KRaft 模式 不依赖 ZooKeeper

1.下载 https://archive.apache.org/dist/kafka/3.9.0/kafka_2.12-3.9.0.tgz2.配置使用 KRaft 模式 2.1 修改 Kafka 的配置文件 cd D:\data\bigdata\kafka_2.12-3.9.0\config\kraft 修改 server.properties # 设置 Kafka 数据日志存储目录 log.dirsD:\\data\\bigdata\\kaf…

Xilinx FPGA | 管脚约束 / 时序约束 / 问题解析

注&#xff1a;本文为 “Xilinx FPGA | 管脚约束 / 时序约束 / 问题解析” 相关文章合辑。 略作重排&#xff0c;未整理去重。 如有内容异常&#xff0c;请看原文。 Xilinx FPGA 管脚 XDC 约束之&#xff1a;物理约束 FPGA技术实战 于 2020-02-04 17:14:53 发布 说明&#x…

Python-JsonRPC

Python-JsonRPC 使用Python学习JsonRPC数据交互 1-核心知识点 1&#xff09;什么是JsonRPC&#xff0c;这种协议是如何工作的&#xff1f;->使用请求进行验证2&#xff09;JsonRPC可以使用Postman进行验证吗&#xff1f;->可以使用POSTMAN进行调用&#xff08;使用HTTP请…

Redis从入门到实战——实战篇(下)

四、达人探店 1. 发布探店笔记 探店笔记类似于点评网站的评价&#xff0c;往往是图文结合。对应的表有两个&#xff1a; tb_blog&#xff1a;探店笔记表&#xff0c;包含笔记中的标题、文字、图片等tb_blog_comments&#xff1a;其他用户对探店笔记的评价 步骤①&#xff1…

面试问题(连载。。。。)

flexbox 和 crid 的区别 1. 布局维度与核心特性 Flexbox&#xff08;弹性盒子&#xff09; 一维布局&#xff1a;专注于行或列的线性排列&#xff0c;适合单方向&#xff08;水平或垂直&#xff09;的布局需求。动态分配空间&#xff1a;通过 flex-grow、flex-shrink 和 flex…

OpenCv实战笔记(1)在win11搭建opencv4.11.1 + qt5.15.2 + vs2019_x64开发环境

一. 准备工作 Visual Studio 2019&#xff08;安装时勾选 C 桌面开发 和 Windows 10 SDK&#xff09; CMake 3.20&#xff08;官网下载&#xff09; Qt 5.15.2&#xff08;下载 Qt Online Installer&#xff09;安装时勾选 MSVC 2019 64-bit 组件。 opencv 4.11.1 源码下载 git…

全局网络:重构数字时代的连接范式

从局部到全局 —— 网络架构的范式革命 在全球化与数字化深度融合的今天&#xff0c;传统网络架构的 “碎片化” 问题日益凸显&#xff1a;跨地域数据流通低效、设备互联孤岛化、安全策略难以统一。 全局网络作为一种突破地域与技术边界的新型网络架构&#xff0c;正成为企业…

C++ Primer (第五版)-第十四章重载运算与类型转换

文章目录 一、基本概念可以被重载某些运算符不应被重载尽量明智使用运算符重载赋值和复合赋值运算符选择作为成员或者非成员 输入和输出运算符输入运算符尽量减少格式化操作输入输出运算符必须是非成员函数 重载输入运算符>>输入时的错误标示错误 算数和关系运算符相等运…

鸿蒙开发——5.ArkUI @Builder装饰器:打造高效可复用的UI组件

鸿蒙开发——5.ArkUI Builder装饰器&#xff1a;打造高效可复用的UI组件 ArkUI Builder装饰器&#xff1a;打造高效可复用的UI组件一、Builder装饰器是什么&#xff1f;二、两种构建函数类型1. 私有自定义构建函数2. 全局自定义构建函数 三、参数传递核心规则1. 按值传递&#…

PyTorchVideo实战:从零开始构建高效视频分类模型

视频理解作为机器学习的核心领域&#xff0c;为动作识别、视频摘要和监控等应用提供了技术基础。本教程将详细介绍如何利用PyTorchVideo和PyTorch Lightning两个强大框架&#xff0c;构建基于Kinetics数据集训练的3D ResNet模型&#xff0c;实现高效的视频分类流程。 PyTorch…

SEMI E40-0200 STANDARD FOR PROCESSING MANAGEMENT(加工管理标准)-(二)

8 行为规范 8.1 本章定义监督实体&#xff08;Supervisor&#xff09;与加工资源&#xff08;Processing Resource&#xff09;为实现物料加工所需的高层级通信逻辑&#xff0c;不涉及具体消息细节&#xff08;详见第10章消息服务&#xff09;。 8.2 加工任务通信 8.2.1 加工…

根据窗口大小自动调整页面缩放比例,并保持居中显示

vue 项目 直接上代码 图片u1.png 是个背景图片 图片u2.png 是个遮罩 <template><div id"app"><div class"viewBox"><divclass"screen":style"{ transform: translate(-50%,-50%…

Android SDK 国内镜像及配置方法(2025最新,包好使!)

2025最新android sdk下载配置 1、首先你需要有android sdk manager2、 直接上教程修改hosts文件配置域名映射即可(不用FQ)2.1 获取ping dl.google.com域名ip地址2.2 配置hosts文件域名映射2.3 可以随意下载你需要的sdk3、 总结:走过弯路,踩过坑!!!大家就不要踩了!避坑1…

【Python开源】深度解析:一款高效音频封面批量删除工具的设计与实现

&#x1f3b5; 【Python开源】深度解析&#xff1a;一款高效音频封面批量删除工具的设计与实现 &#x1f308; 个人主页&#xff1a;创客白泽 - CSDN博客 &#x1f525; 系列专栏&#xff1a;&#x1f40d;《Python开源项目实战》 &#x1f4a1; 热爱不止于代码&#xff0c;热情…

OpenStack Yoga版安装笔记(26)实例元数据笔记

一、实例元数据概述 1.1 元数据 &#xff08;官方文档&#xff1a;Metadata — nova 25.2.2.dev5 documentation&#xff09; Nova 通过一种叫做元数据&#xff08;metadata&#xff09;的机制向其启动的实例提供配置信息。这些机制通常通过诸如 cloud-init 这样的初始化软件…

【Linux】swap交换分区管理

目录 一、Swap 交换分区的功能 二、swap 交换分区的典型大小的设置 2.1 查看交换分区的大小 2.1.1 free 2.1.2 cat /proc/swaps 或 swapon -s 2.1.3 top 三、使用交换分区的整体流程 3.1 案例一 3.2 案例二 一、Swap 交换分区的功能 计算机运行一个程序首先会将外存&am…

VirtualBox 创建虚拟机并安装 Ubuntu 系统详细指南

VirtualBox 创建虚拟机并安装 Ubuntu 系统详细指南 一、准备工作1. 下载 Ubuntu 镜像2. 安装 VirtualBox二、创建虚拟机1. 新建虚拟机2. 分配内存3. 创建虚拟硬盘三、配置虚拟机1. 加载 Ubuntu 镜像2. 调整处理器核心数(可选)3. 启用 3D 加速(图形优化)四、安装 Ubuntu 系统…

触想CX-3588工控主板应用于移动AI数字人,赋能新型智能交互

一、行业发展背景 随着AI智能、自主导航和透明屏显示等技术的不断进步&#xff0c;以及用户对“拟人化”、“沉浸式”交互体验的期待&#xff0c;一种新型交互终端——“移动AI数字人”正在加速实现规模化商用。 各大展厅展馆、零售导购、教学政务甚至家庭场景中&#xff0c;移…