动态规划详解:最长公共子序列问题的高效解法

news2024/10/21 11:05:56

在编程中,子序列问题经常出现,并且它们涉及到字符串的处理与算法优化。今天我们来探讨一个经典的算法问题:最长公共子序列(Longest Common Subsequence,LCS)。这一问题不仅是面试中的高频考题,也是动态规划的典型应用场景。本文将详细介绍这个问题的解决方案,特别是如何使用动态规划来有效求解。


1. 问题描述

最长公共子序列问题要求在给定的两个字符串中找到它们的最长公共子序列。子序列的定义是从一个字符串中删除部分字符(也可以不删除),并保持字符的相对顺序,生成的新字符串。因此,最长公共子序列是指两个字符串中的最长的相同子序列。

示例

  • 示例 1
    输入:text1 = "abcde"text2 = "ace"
    输出:3
    解释:"ace"text1text2 的最长公共子序列,长度为 3。

  • 示例 2
    输入:text1 = "abc"text2 = "abc"
    输出:3
    解释:最长公共子序列是 "abc",长度为 3。

  • 示例 3
    输入:text1 = "abc"text2 = "def"
    输出:0
    解释:两个字符串没有公共子序列,因此返回 0。

1143. 最长公共子序列 - 力扣(LeetCode)


2. 动态规划解法

2.1 动态规划的核心思想

动态规划(Dynamic Programming)是解决最优化问题的一种有效方法,核心思想是通过拆解子问题并存储子问题的结果,避免重复计算。对于最长公共子序列问题,我们可以通过逐步构建子问题的解来得到整个问题的解。

2.2 最优子结构

最长公共子序列问题具有最优子结构,这意味着更大的问题可以由较小的子问题递归地解决。具体而言:

  • 如果 text1[i-1] == text2[j-1],那么最长公共子序列的长度可以从两个字符串都去掉最后一个字符后的最长公共子序列长度加 1 得到。
  • 如果 text1[i-1] != text2[j-1],则最长公共子序列应该是去掉 text1 的最后一个字符或者去掉 text2 的最后一个字符后的两个子问题的最大值。

2.3 状态定义

我们可以定义一个二维数组 dp,其中 dp[i][j] 表示 text1[0:i]text2[0:j] 的最长公共子序列的长度。

2.4 状态转移方程

根据最优子结构性质,状态转移方程可以表示为:

d p [ i ] [ j ] = { d p [ i − 1 ] [ j − 1 ] + 1 , if  t e x t 1 [ i − 1 ] = = t e x t 2 [ j − 1 ] max ⁡ ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) , if  t e x t 1 [ i − 1 ] ≠ t e x t 2 [ j − 1 ] dp[i][j] = \begin{cases} dp[i-1][j-1] + 1, & \text{if } text1[i-1] == text2[j-1] \\ \max(dp[i-1][j], dp[i][j-1]), & \text{if } text1[i-1] \neq text2[j-1] \end{cases} dp[i][j]={dp[i1][j1]+1,max(dp[i1][j],dp[i][j1]),if text1[i1]==text2[j1]if text1[i1]=text2[j1]

  • 当两个字符串的最后一个字符相同时,当前最长公共子序列的长度等于去掉最后一个字符后子问题的长度加 1。
  • 如果最后一个字符不相同,则取两个较小子问题的最大值。

2.5 初始条件

i == 0j == 0 时,即其中一个字符串为空时,最长公共子序列的长度为 0,因此有:

d p [ 0 ] [ j ] = d p [ i ] [ 0 ] = 0 dp[0][j] = dp[i][0] = 0 dp[0][j]=dp[i][0]=0

2.6 代码实现

#include <iostream>
#include <vector>
using namespace std;

int longestCommonSubsequence(string text1, string text2) {
    int m = text1.length();
    int n = text2.length();
    
    // 创建 dp 数组,dp[i][j] 表示 text1[0:i] 和 text2[0:j] 的最长公共子序列长度
    vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
    
    // 填充 dp 数组
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (text1[i - 1] == text2[j - 1]) {
                // 如果最后一个字符相同,公共子序列长度加1
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                // 如果最后一个字符不同,取两个可能的子问题中的较大值
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    
    // dp[m][n] 就是两个字符串的最长公共子序列长度
    return dp[m][n];
}

int main() {
    string text1 = "abcde";
    string text2 = "ace";
    cout << "最长公共子序列的长度: " << longestCommonSubsequence(text1, text2) << endl;
    return 0;
}

2.7 示例讲解

示例 1:

输入:text1 = "abcde"text2 = "ace"
输出:3
解释:最长公共子序列是 "ace",它的长度为 3

DP数组计算过程

ace
0000
a0111
b0111
c0122
d0122
e0123

从表格可以看出,dp[5][3] = 3,即 text1 = "abcde"text2 = "ace" 的最长公共子序列长度为3。

示例 2:

输入:text1 = "abc"text2 = "abc"
输出:3
解释:最长公共子序列是 "abc",它的长度为 3

DP数组计算过程

abc
0000
a0111
b0122
c0123

这里,text1text2 是完全相同的,因此最长公共子序列是 "abc",长度为 3

示例 3:

输入:text1 = "abc"text2 = "def"
输出:0
解释:两个字符串没有公共子序列,返回 0

DP数组计算过程

def
0000
a0000
b0000
c0000

从表格可以看出,dp[3][3] = 0,即没有公共子序列。


3. 递归解法(带备忘录)

递归解法虽然直观易懂,但直接递归会导致大量重复计算。为避免这种重复计算,我们可以使用备忘录来存储已经计算过的子问题的结果,从而提高效率。

3.1 递归代码实现

#include <iostream>
#include <vector>
using namespace std;

int dfs(string& text1, string& text2, int i, int j, vector<vector<int>>& memo) {
    // 如果 i 或 j 小于 0,说明已经到达字符串的边界,返回 0
    if (i < 0 || j < 0) {
        return 0;
    }
    // 如果已经计算过这个子问题,直接返回备忘录中的结果
    if (memo[i][j] != -1) {
        return memo[i][j];
    }
    // 如果最后一个字符相同,递归求解并加1
    if (text1[i] == text2[j]) {
        memo[i][j] = dfs(text1, text2, i - 1, j - 1, memo) + 1;
    } else {
        // 如果最后一个字符不同,选择去掉一个字符的两个子问题的较大值
        memo[i][j] = max(dfs(text1, text2, i - 1, j, memo), dfs(text1, text2, i, j - 1, memo));
    }
    return memo[i][j];
}

int longestCommonSubsequence(string text1, string text2) {
    int m = text1.length();
    int n = text2.length();
    // 初始化 memo 数组,-1 表示还没有计算过
    vector<vector<int>> memo(m, vector<int>(n, -1));
    return dfs(text1, text2, m - 1, n - 1, memo);
}

int main() {
    string text1 = "abcde";
    string text2 = "ace";
    cout << "最长公共子序列的长度: " << longestCommonSubsequence(text1, text2) << endl;
    return 0;
}

4. 总结

  • 动态规划是解决最长公共子序列问题的最优解法,时间复杂度为 O(m * n),空间复杂度也是 O(m * n)。通过二维数组存储子问题的解,可以避免大量的重复计算。
  • **递归法(带备忘录)**也是一种可行的方案,它通过递归来逐步求解子问题,使用备忘录存储中间结果,从而避免重复计算,时间复杂度同样为 O(m * n)

总体而言,动态规划是一种更加直观、效率高的解法,对于初学者来说也是更容易掌握的。

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

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

相关文章

python爬虫快速入门之---Scrapy 从入门到包吃包住

python爬虫快速入门之—Scrapy 从入门到包吃包住 文章目录 python爬虫快速入门之---Scrapy 从入门到包吃包住一、scrapy简介1.1、scrapy是什么?1.2、Scrapy 的特点1.3、Scrapy 的主要组件1.4、Scrapy 工作流程1.5、scrapy的安装 二、scrapy项目快速入门2.1、scrapy项目快速创建…

Spring Boot框架下JavaWeb在线考试系统的创新实现

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

JavaFx学习--chapter02(网络对话)

chapter02(网络对话) 简单网络对话程序 设计任务&#xff1a;客户端向服务器发送字符串&#xff0c;并能读取服务器返回的字符串。 知识点&#xff1a;TCP套接字技术&#xff0c;C/S软件架构程序设计 重点理解&#xff1a;Java客户套接字类Socket和服务器套接字类ServerSoc…

docker配置加速器

阿里云 控制台》容器镜像服务》镜像工具》镜像加速器 复制地址&#xff1a;https://ywtoq7bz.mirror.aliyuncs.com 到&#xff1a;etc/docker下&#xff1a;vi daemon.json 格式&#xff1a; { "registry-mirrors": ["加速器地址"] } 注&#xff1…

Visual Studio 2022安OpenCV可视化工具image watch

1. 打开 VS2022 &#xff0c;扩展 -管理扩展 2. 搜索 Image Watch 关闭VS2022 后 安装 打开视图、调出 Image Watch 窗口 测试代码&#xff1a; #include "opencv2/imgproc.hpp" #include "opencv2/imgcodecs.hpp" #include "opencv2/highgui.…

Transformer(Vit+注意力机制)

文献基本信息&#xff1a; Encoder-Decoder&#xff1a; Transformer的结构&#xff1a; 输入编码器解码器输出 Transformer的工作流程&#xff1a; 获取输入句子的每一个单词的表示向量X&#xff0c;X由单词的embedding&#xff08;embedding是一种将高维特征映射到低维的技…

opencv出错以及解决技巧

opencv配置 一开始&#xff0c;include的路径是<opencv4/opencv2/…> 这样在using namespace cv的时候导致了报错&#xff0c; 所以在cmakelist中需要对cmake的版本进行升级。 set(CMAKE_CXX_FLAGS “-stdc14 -O0 -Wall”)-O0 表示在编译过程中不进行任何优化 对应的pac…

Linux操作系统如何制作U盘启动盘

在麒麟系统中有一款U盘启动器软件&#xff0c;它是用于制作系统启动U盘的工具&#xff0c;方便无光驱的电脑安装操作系统&#xff0c;也可以反复使用一个U盘&#xff0c;避免光盘的浪费。下面对该U盘启动器使用方法做详细讲解。 1.准备需要安装的系统镜像文件。 图 1 2.准备1…

Node-RED开源项目的modbus通信(TCP)

一、Modbus 通信协议 Modbus是一种串行通信协议&#xff0c;是Modicon公司&#xff08;现在的施耐德电气 Schneider Electric&#xff09;于1979年为使用可编程逻辑控制器&#xff08;PLC&#xff09;通信而发表。Modbus已经成为工业领域通信协议的业界标准&#xff08;De fact…

Redis高阶篇之Redis单线程与多线程

文章目录 0 前言1. 为什么Redis是单线程&#xff1f;1.1 Redis单线程1.2 为什么Redis3时代单线程快的原因1.3 使用单线程原因 2.为什么逐渐加入多线程呢&#xff1f;2.1 如何解决 3.redis6/7的多线程特性和IO多路复用入门3.1主线程和IO线程怎么协作完成请求处理的3.2 Unix网络编…

政府采购合同公告明细数据(1996-2024年)

透明度成为了公众对政府活动的基本要求之一。特别是在政府采购领域&#xff0c;透明度不仅关系到公共资源的合理分配&#xff0c;更是维护市场公平竞争的重要保障。政府采购合同公告制度正是为了满足这一需求而设立的。 1996-2024年政府采购合同公告明细数据&#xff08;dta文…

Perl打印9x9乘法口诀

本章教程主要介绍如何用Perl打印9x9乘法口诀。 一、程序代码 1、写法① use strict; # 启用严格模式&#xff0c;帮助捕捉变量声明等错误 use warnings; # 启用警告&#xff0c;帮助发现潜在问题# 遍历 1 到 9 的数字 for my $i (1..9) {# 对于每个 $i&#xff0c;遍历 1…

Javascript 脚本查找B站限时免费番剧

目录 前言 脚本编写 脚本 前言 B站的一些番剧时不时会“限时免费”&#xff0c;白嫖党最爱&#xff0c;主打一个又占到便宜的快乐。但是在番剧索引里却没有搜索选项可以直接检索“限时免费”的番剧&#xff0c;只能自己一页一页的翻去查看&#xff0c;非常麻烦。 自己找限…

Git极速入门

git初始化 git -v git config --global user.name "" git config --global user.email "" git config --global credential.helper store git config --global --list省略(Local) 本地配置&#xff0c;只对本地仓库有效–global 全局配置&#xff0c;所有…

spring boot yml文件中引用*.properties文件中的属性

1、首先在*.properties文件中加入一个属性&#xff0c;如&#xff1a; 2、然后再application.yml文件中通过${jdbc.driver}来引用&#xff0c;如&#xff1a; 3、然后再创建一个资源配置类&#xff0c;通过PropertySource来引入这个*.properties文件&#xff0c;如&#xff1…

JDK中socket源码解析

目录 1、Java.net包 1. Socket通信相关类 2. URL和URI处理类 3. 网络地址和主机名解析类 4. 代理和认证相关类 5. 网络缓存和Cookie管理类 6. 其他网络相关工具类 2、什么是socket&#xff1f; 3、JDK中socket核心Api 4、核心源码 1、核心方法 2、本地方法 3、lin…

基于stm32的esp8266的WIFI控制风扇实验

实验案例&#xff37;&#xff29;&#xff26;&#xff29;控制风扇 项目需求 电脑通过esp8266模块远程遥控风扇。 项目框图 ​ 风扇模块封装 #include "sys.h" #include "fan.h"void fan_init(void) {GPIO_InitTypeDef gpio_initstruct;//打开时钟…

4K Mini-LED显示器平民价,一千多的联合创新27M3U到底有多香

哈喽小伙伴们好&#xff0c;我是Stark-C~ 要说前几年买显示器还是普通IPS的天下&#xff0c;那个时候虽说也有MiniLED或者OLED显示器&#xff0c;但是价格那也是真贵啊&#xff0c;毕竟那个时候MiniLED和OLED还没普及&#xff0c;只有一些高档电视或者显示器才会用到此技术。不…

OpenCV高级图形用户界面(18)手动设置轨迹条(Trackbar)的位置函数setTrackbarPos()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 该函数设置指定窗口中指定轨迹条的位置。 注意 [仅 Qt 后端] 如果轨迹条附加到控制面板&#xff0c;则 winname 可以为空。 函数原型 void cv…

三周精通FastAPI:4 使用请求从客户端(例如浏览器)向 API 发送数据

FastAPI官网手册&#xff1a;https://fastapi.tiangolo.com/zh/tutorial/query-params/ 上节内容&#xff1a;三周精通FastAPI&#xff1a;3 查询参数 请求 FastAPI 使用请求从客户端&#xff08;例如浏览器&#xff09;向 API 发送数据。 请求是客户端发送给 API 的数据。响…