动态规划-最长公共子序列

题目

最长公共子序列
给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。

若这两个字符串没有公共子序列,则返回 0。

示例 1:

输入:text1 = “abcde”, text2 = “ace”
输出:3
解释:最长公共子序列是 “ace”,它的长度为 3。
示例 2:

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

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

1 <= text1.length <= 1000
1 <= text2.length <= 1000 输入的字符串只含有小写英文字符。

思路

状态表示

我们可以使用动态规划(Dynamic Programming, DP)来解决这个问题。具体来说,我们使用一个二维数组 dp[i][j] 来表示 text1[0..i-1]text2[0..j-1] 的最长公共子序列的长度。

  • dp[i][j] 表示 text1 的前 i 个字符与 text2 的前 j 个字符的最长公共子序列的长度。

状态计算

dp
在这里插入图片描述
\

四种情况分析

让我们详细分析这四种情况,以及它们如何影响 dp[i][j] 的值:

1. 00(不包含 text1[i-1]text2[j-1]

这意味着我们在计算 dp[i][j] 时既不包含 text1[i-1] 也不包含 text2[j-1],即我们从 dp[i-1][j-1] 继承。

但这里需要注意的是,这种情况实际上是“隐式”包含在下面两种情况中的,因此我们不需要单独列出:

  • 情况 1: 如果 text1[i-1] != text2[j-1],则最长公共子序列不会包括 text1[i-1]text2[j-1],我们需要从两个方向选择一个较大的值,表示我们“跳过”了这两个字符,即 dp[i][j] = max(dp[i-1][j], dp[i][j-1])
2. 01(不包含 text1[i-1],包含 text2[j-1]
  • 情况 2: 如果我们不考虑 text1[i-1],但考虑 text2[j-1],则 dp[i][j] = dp[i][j-1],即我们不包括 text1[i-1],但可能从 dp[i][j-1] 中获得结果。
3. 10(包含 text1[i-1],不包含 text2[j-1]
  • 情况 3: 如果我们不考虑 text2[j-1],但考虑 text1[i-1],则 dp[i][j] = dp[i-1][j],即我们不包括 text2[j-1],但可能从 dp[i-1][j] 中获得结果。
4. 11(包含 text1[i-1]text2[j-1]
  • 情况 4: 如果 text1[i-1] == text2[j-1],那么我们可以同时包含 text1[i-1]text2[j-1],此时 dp[i][j] = dp[i-1][j-1] + 1

合并这些情况

综合这些情况,我们可以得出状态转移方程:

  • 如果 text1[i-1] == text2[j-1],则:
    d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 dp[i][j] = dp[i-1][j-1] + 1 dp[i][j]=dp[i1][j1]+1
  • 如果 text1[i-1] != text2[j-1],则:
    d p [ i ] [ j ] = max ⁡ ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) dp[i][j] = \max(dp[i-1][j], dp[i][j-1]) dp[i][j]=max(dp[i1][j],dp[i][j1])

状态转移的总结

我们不需要显式地处理 00 这种情况,因为它已经包含在 0110 中。当字符不相等时,我们从 dp[i-1][j]dp[i][j-1] 中选取最大值,表示我们跳过了某个字符。

例子

考虑以下示例,text1 = "abcde"text2 = "ace"

“”ace
“”0000
a0111
b0111
c0122
d0122
e0123
  • dp[1][1] = 1,表示 "a""a" 匹配,公共子序列长度为 1。
  • dp[2][2] = 1,表示 "b""c" 不匹配,最长公共子序列仍然是之前的 "a"
  • dp[3][3] = 2,表示 "c""e" 匹配,公共子序列变为 "ac"
  • 最终的 dp[5][3] = 3,表示 "abcde""ace" 的最长公共子序列长度为 3,子序列为 "ace"
    在这里插入图片描述

DP 数组的初始化

DP 数组 dp[i][j] 的定义是:dp[i][j] 表示 text1[0..i-1]text2[0..j-1] 的最长公共子序列的长度。

初始化步骤
  1. text1text2 中有一个字符串为空时,显然其与另一个字符串的公共子序列的长度为 0。因此,所有的 dp[0][j]dp[i][0] 都应该初始化为 0。

    • dp[0][j] = 0,表示当 text1 为空时,任何 text2 子串的最长公共子序列长度为 0。
    • dp[i][0] = 0,表示当 text2 为空时,任何 text1 子串的最长公共子序列长度为 0。
  2. 一般情况下,初始化二维 DP 数组的过程如下:

    • 创建一个大小为 (m+1) x (n+1) 的二维数组,其中 mtext1 的长度,ntext2 的长度。
    • 初始化 dp[0][j]dp[i][0] 为 0。

代码实现

def longestCommonSubsequence(text1: str, text2: str) -> int:m, n = len(text1), len(text2)# 初始化 DP 数组,大小为 (m+1) x (n+1)dp = [[0] * (n + 1) for _ in range(m + 1)]# 填充 DP 数组for i in range(1, m + 1):for j in range(1, n + 1):if text1[i - 1] == text2[j - 1]:# 如果字符相等,当前公共子序列的长度加1dp[i][j] = dp[i - 1][j - 1] + 1else:# 如果字符不等,取最大值,表示跳过当前字符dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])# 返回最终的结果return dp[m][n]

代码解析

  1. 初始化 DP 数组

    • 使用 dp = [[0] * (n + 1) for _ in range(m + 1)] 初始化了一个大小为 (m+1) x (n+1) 的二维数组,dp[i][j] 用来存储 text1[0..i-1]text2[0..j-1] 的最长公共子序列长度。
    • 初始化时,dp[i][0]dp[0][j] 都被设置为 0,表示当任意一个字符串为空时,最长公共子序列的长度为 0。
  2. DP 填充过程

    • 对于每一对字符 text1[i-1]text2[j-1],如果字符相等,那么 dp[i][j] = dp[i-1][j-1] + 1
    • 如果字符不等,dp[i][j] = max(dp[i-1][j], dp[i][j-1]),表示我们要么跳过 text1[i-1],要么跳过 text2[j-1]
  3. 返回结果

    • 最终的最长公共子序列的长度存储在 dp[m][n] 中,即 text1[0..m-1]text2[0..n-1] 的最长公共子序列长度。

时间和空间复杂度

  • 时间复杂度:O(m * n),我们需要遍历 text1text2 的每一对字符,并更新 DP 表。
  • 空间复杂度:O(m * n),我们使用了一个二维数组来存储所有子问题的解。

空间优化

如果只关心当前行和上一行的状态,我们可以优化空间复杂度,使用滚动数组(两个一维数组)代替二维数组,减少空间消耗。

def longestCommonSubsequence(text1: str, text2: str) -> int:m, n = len(text1), len(text2)# 初始化两个一维数组,分别用于存储上一行和当前行prev = [0] * (n + 1)curr = [0] * (n + 1)# 填充 DP 数组for i in range(1, m + 1):for j in range(1, n + 1):if text1[i - 1] == text2[j - 1]:curr[j] = prev[j - 1] + 1else:curr[j] = max(prev[j], curr[j - 1])# 将当前行的结果存入上一行,准备计算下一行prev, curr = curr, prev# 返回最终结果return prev[n]

好的!下面我将为你提供 GoC++ 语言的代码实现,帮助你在这两种语言中解决 最长公共子序列(LCS)问题。

Go 语言实现

package mainimport "fmt"func longestCommonSubsequence(text1 string, text2 string) int {m, n := len(text1), len(text2)// 初始化一个二维数组 dp,用于存储子问题的结果dp := make([][]int, m+1)for i := range dp {dp[i] = make([]int, n+1)}// 填充 dp 数组for i := 1; i <= m; i++ {for j := 1; j <= n; j++ {if text1[i-1] == text2[j-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]
}// 辅助函数:返回两个整数中的较大者
func max(a, b int) int {if a > b {return a}return b
}func main() {text1 := "abcde"text2 := "ace"fmt.Println(longestCommonSubsequence(text1, text2))  // 输出 3
}

C++ 语言实现

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;int longestCommonSubsequence(string text1, string text2) {int m = text1.size(), n = text2.size();// 创建一个 (m+1) x (n+1) 的二维 dp 数组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]) {dp[i][j] = dp[i - 1][j - 1] + 1;  // 如果字符相等,公共子序列长度加 1} else {dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);  // 否则取最大值}}}// 返回最长公共子序列的长度return dp[m][n];
}int main() {string text1 = "abcde";string text2 = "ace";cout << longestCommonSubsequence(text1, text2) << endl;  // 输出 3return 0;
}

代码解释

  1. 初始化 DP 数组
    在这两个实现中,我们都创建了一个二维数组 dpdp[i][j] 表示 text1[0..i-1]text2[0..j-1] 的最长公共子序列的长度。数组的尺寸为 (m+1) x (n+1)mn 分别是两个字符串的长度。dp[i][0]dp[0][j] 都初始化为 0,表示当某个字符串为空时,最长公共子序列的长度为 0。

  2. 填充 DP 表

    • 我们遍历每一对字符 text1[i-1]text2[j-1],如果这两个字符相等,那么公共子序列的长度是 dp[i-1][j-1] + 1,否则取 dp[i-1][j]dp[i][j-1] 的最大值,表示我们跳过了其中一个字符。
  3. 返回结果
    最终,dp[m][n] 存储的就是 text1text2 的最长公共子序列的长度。

Go 语言优化版:
package mainimport "fmt"func longestCommonSubsequence(text1 string, text2 string) int {m, n := len(text1), len(text2)// 保留上一行和当前行的数组prev := make([]int, n+1)curr := make([]int, n+1)// 填充 DP 数组for i := 1; i <= m; i++ {for j := 1; j <= n; j++ {if text1[i-1] == text2[j-1] {curr[j] = prev[j-1] + 1} else {curr[j] = max(prev[j], curr[j-1])}}prev, curr = curr, prev  // 切换当前行和上一行}return prev[n]  // 返回结果
}func max(a, b int) int {if a > b {return a}return b
}func main() {text1 := "abcde"text2 := "ace"fmt.Println(longestCommonSubsequence(text1, text2))  // 输出 3
}
C++ 语言优化版:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;int longestCommonSubsequence(string text1, string text2) {int m = text1.size(), n = text2.size();// 初始化两行数组vector<int> prev(n + 1, 0), curr(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]) {curr[j] = prev[j - 1] + 1;} else {curr[j] = max(prev[j], curr[j - 1]);}}prev = curr;  // 切换当前行和上一行}return prev[n];  // 返回结果
}int main() {string text1 = "abcde";string text2 = "ace";cout << longestCommonSubsequence(text1, text2) << endl;  // 输出 3return 0;
}

参考

  1. acwingyxc老师
  2. 代码随想录

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

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

相关文章

nvm安装node遇到的若干问题(vscode找不到npm文件、环境变量配置混乱、npm安装包到D盘)

问题一&#xff1a;安装完nvm后需要做哪些环境变量的配置&#xff1f; 1.打开nvm文件夹下的setting文件&#xff0c;设置nvm路径和安装node路径&#xff0c;并添加镜像。 root: D:\software\nvm-node\nvm path: D:\software\nvm-node\nodejs node_mirror: https://npmmirror.c…

Zookeeper的简单使用Centos环境下

目录 前言 一、ZOokeeper是什么&#xff1f; 二、安装Zookeeper 1.进入官网下载 2.解压到服务器 3.配置文件 三.使用Zookeeper 3.1启动相关指令 3.2其他指令 3.3ACL权限 总结 前言 记录下安装zookeeper的一次经历 一、ZOokeeper是什么&#xff1f; ZooKeeper是一…

疫情中的图书馆管理:Spring Boot系统设计

摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了疫情下图书馆管理系统的开发全过程。通过分析疫情下图书馆管理系统管理的不足&#xff0c;创建了一个计算机管理疫情下图书馆管理系统的方案。文章介绍了疫情下图…

Excel数据动态获取与映射

处理代码 动态映射 动态读取 excel 中的数据&#xff0c;并通过 json 配置 指定对应列的值映射到模板中的什么字段上 private void GetFreightFeeByExcel(string filePath) {// 文件名需要以快递公司命名 便于映射查询string fileName Path.GetFileNameWithoutExtension(fi…

网络(TCP)

目录 TCP socket API 详解 bind(): 我们的程序中对myaddr参数是这样初始化的: listen(): accept(): 理解accecpt的返回值: 饭店拉客例子 connect tcp服务器和udp类似的部分代码 把套接字设置为监听状态&#xff08;listen&#xff09; 测试 查看端口号和IP地址&…

了解鱼叉式网络钓鱼攻击的社会工程学元素

一提到网络攻击&#xff0c;你可能会想象一个老练的黑客躲在类似《黑客帝国》的屏幕后面&#xff0c;利用自己的技术实力积极入侵网络。然而&#xff0c;许多攻击的现实情况远比这平凡得多。 一封带有“未送达”等无害主题的简单电子邮件被放在员工的垃圾邮件文件夹中。他们心…

TS流详解

目录 TS流结构 PSI 节目关联表&#xff08;PAT Program Association Table&#xff09; 条件接收表&#xff08;CAT Conditional Access Table&#xff09; 节目映射表&#xff08;PMT Program Map Table&#xff09; 网络信息表&#xff08;NIT Nerwork Information Tabl…

【图像处理识别】数据集合集!

本文将为您介绍经典、热门的数据集&#xff0c;希望对您在选择适合的数据集时有所帮助。 1 CNN-ImageProc-Robotics 机器人 更新时间&#xff1a;2024-07-29 访问地址: GitHub 描述&#xff1a; 通过 CNN 和图像处理进行机器人对象识别项目侧重于集成最先进的深度学习技术和…

C语言--分支循环编程题目

第一道题目&#xff1a; #include <stdio.h>int main() {//分析&#xff1a;//1.连续读取int a 0;int b 0;int c 0;while (scanf("%d %d %d\n", &a, &b, &c) ! EOF){//2.对三角形的判断//a b c 等边三角形 其中两个相等 等腰三角形 其余情…

Windows系统使用全功能的跨平台开源音乐服务器Navidrome搭建在线音乐库

文章目录 前言1. 安装Docker2. Docker镜像源添加方法3. 创建并启动Navidrome容器4. 公网远程访问本地Navidrome4.1 内网穿透工具安装4.2 创建远程连接公网地址4.3 使用固定公网地址远程访问 前言 在数字时代&#xff0c;拥有一个个性化、便捷的音乐库成为了许多人的需求。本文…

监控易DEMO功能深度解析:运维行业的智能化转型新助力

在数字化转型的浪潮中&#xff0c;运维行业正面临着前所未有的变革与挑战。为了应对日益复杂的IT架构和不断提升的运维需求&#xff0c;监控易的集中式跨平台一体化监控软件不断升级优化&#xff0c;以适应新的运维环境。本文将对监控易DEMO的功能进行深度解析&#xff0c;探讨…

ElasticSearch学习篇17_《检索技术核心20讲》最邻近检索-局部敏感哈希、乘积量化PQ思路

目录 场景在搜索引擎和推荐引擎中&#xff0c;对相似文章去重是一个非常重要的环节&#xff0c;另外是拍照识花、摇一摇搜歌等场景都可以使用它快速检索。 基于敏感性哈希的检索更擅长处理字面上的相似而不是语义上的相似。 向量空间模型ANN检索加速思路 局部敏感哈希编码 随…

SpringBoot MySQL的增删改查

创建数据库和表 安装MySQL&#xff1a;https://blog.csdn.net/qq_59636442/article/details/141926105 通过Navicat 创建数据库test 创建表student后随便添加一条数据 CREATE TABLE student (id int NOT NULL AUTO_INCREMENT,name varchar(255) DEFAULT NULL,email varchar(2…

23种设计模式-备忘录(Memento)设计模式

文章目录 一.什么是备忘录设计模式&#xff1f;二.备忘录模式的特点三.备忘录模式的结构四.备忘录模式的优缺点五.备忘录模式的 C 实现六.备忘录模式的 Java 实现七.总结 类图&#xff1a; 备忘录设计模式类图 一.什么是备忘录设计模式&#xff1f; 备忘录设计模式&#xff08…

如何删除pdf里的任意一页?删除PDF里任意一页的几种方法

如何删除pdf里的任意一页&#xff1f;尽管PDF文件具有许多优点&#xff0c;如跨平台兼容性和格式保真性&#xff0c;但在编辑和修改方面&#xff0c;它与像Word或Excel这类文档格式不同&#xff0c;通常不能像其他文档那样轻松进行直接的内容删除或修改。这让很多人以为&#x…

css3新特性(二十六课)

1、css3盒子模型 box - sizing: content - box&#xff1b; 是 CSS 中用于定义盒模型宽度和高度计算方式的一个属性值。在这种盒模型下&#xff0c;元素的宽度和高度&#xff08;width和height属性&#xff09;仅包括内容区域&#xff08;content&#xff09;的大小&#xff…

Centos7安装Jenkins脚本一键部署

公司原先Jenkins二进制安装&#xff0c;自己闲来无事在测试主机优化了一下&#xff0c;一键部署&#xff0c;jenkins2.426版本jdk11版本 #!/bin/bashjenkins_file"jenkins-2.426.3-1.1.noarch.rpm"# 更新软件包列表 echo "更新软件包列表..." sudo yum up…

类与对象(c++)——取地址运算符重载,初始化列表,类型转换

1.取地址运算符重载 1.1 const成员函数 a)将const修饰的成员函数称之为const成员函数&#xff0c;const修饰成员函数放到成员函数参数列表的后 ⾯。 b)const实际修饰该成员函数隐含的this指针&#xff0c;表明在该成员函数中不能对类的任何成员进⾏修改。 const 修饰Date类的…

形态学图像处理(Morphological Image Processing)

形态学图像处理(Morphological Image Processing) 前言 ‍ 本博客为个人总结数字图像处理一课所写&#xff0c;并给出适当的扩展和相应的demo。 写博客跟做 checkpoint​ 很像&#xff0c;毕竟个人还不能达到那种信手拈来的境界&#xff0c;忘了就是从零开始训练&#xff0…

如何在K8s集群中管理与使用GPU

背景 随着人工智能的兴起&#xff0c;GPU作为重要的智算算力类型愈发受到重视&#xff0c;而Kubernetes&#xff08;k8s&#xff09;作为业界主流的集群管理系统&#xff0c;如何方便管理、使用GPU也是其需要解决的一大问题&#xff0c;故此收集整理了K8s管理与使用GPU的相关资…