【完虐算法】「字符串-最长公共子序列」全面总结

整体关于字符串「最长公共子序列」方面的问题一般来说都会用动态规划的思想去解决!

下面会通过一个典型案例具体来看是怎么解决的,使用 LeetCode 的 1143 题 进行举例。

1143.最长公共子序列【中等】

1143.最长公共子序列【中等】

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

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

例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。

两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

输入:text1 = “abcde”, text2 = “ace”

输出:3

解释:最长公共子序列是 “ace” ,它的长度为 3 。

这里用动态规划的思想进行解决。对于两个字符串的比较,一定会涉及到利用二维数组来进行解决。

按照之前说的四步骤, 动态数组定义 、 初始化 、 状态转移方程 、 优化 、、。

以下利用字符串 “abcde" 和 ”ace” 为例,计算最长公共子序列。

令 s1=“abcde”, s2=“ace”

一、动态数组定义

根据 s1 的长度为5, s2 的长度为3。

初始化一个 3 行 5 列的二维数组 dp ,赋初值全为 0。用来存储动态规划过程中记录的值。

KaTeX parse error: Undefined control sequence: \[ at position 3: dp\̲[̲i\]\[j\] 代表位置 ( i , j ) (i, j) (i,j) 最长公共子序列的值!

二、初始化

针对动态规划一般的初始化方法,一定是边界的初始化。

这里是二维数组,咱们这里要初始化的地方是第 0 行和第 0 列。

① 针对 0 行

s1=“abcde”, s2=“ace”,s2[0] 与 s1 的每一个字符进行比较,只有第 0 位置字符是相同的,第 0 位置为 1。由于公共子序列的规则,那第 0 行初始化全为 1。

① 针对 0 列

s1=“abcde”, s2=“ace”,s1[0] 与 s2 的每一个字符进行比较,同样只有第 0 位置字符是相同的,第 0 位置为 1。依然是由于公共子序列的规则那第 0 列初始化全为 1。

根据初始化的情况,下面用代码描述:

if text1[0] == text2[0]:

dp[0][0] = 1

for i in range(1, size1):

dp[0][i] = 1 if dp[0][i-1] == 1 else int(text1[i] == text2[0])

for j in range(1, size2):

dp[j][0] = 1 if dp[j-1][0] == 1 else int(text2[j] == text1[0])

三、状态转移方程

很明显可以分为两种情况:

  • 当前位置的字符相同

  • 当前位置的字符不相同

① 当 text1[i] == text2[j] 的时候,说明当前字符相同,只要将上一个字符对应的 dp 的值加 1 就可以了。即 dp[i][j] = dp[i-1][j-1] + 1。

注意,这里一定是 dp[i-1][j-1],因为此处的字符相同。那么,要想计算此处 dp[i][j] 的值,一定是与位置 i-1 与 j-1 的位置相关的。

② 当 text1[i] != text2[j] 的时候,此时当前字符不相同,那么此处 dp[i][j] 的数值一定沿用上一个 dp 的数值。所以,取得一定是其中的最大值 max(dp[i-1][j], dp[i][j-1])。

综上所述,可以得到该问题的状态转移方程:

$$

dp[i][j]= \begin{cases}

dp[i-1][j-1] + 1, & text1[i] = text2[j] \[2ex]

max(dp[i-1][j], dp[i][j-1]), & text1[i] \neq text2[j]

\end{cases}

$$

从位置 (1, 1) 位置开始计算,判断两个字符串在当前字符串是否相等:

  • 如果相等,则取dp[i-1][j-1]+1

  • 如果不相等,则取max(dp[i][j-1], dp[i-1][j])

根据上述的状态转移方程,以及s1=“abcde”, s2=“ace”,下面把二维数组填满,得到最后的答案!

① 位置(1,1):KaTeX parse error: Undefined control sequence: \[ at position 3: s1\̲[̲1\] \\neq s2\[1…

② 位置(1,2):KaTeX parse error: Undefined control sequence: \[ at position 3: s1\̲[̲2\] == s2\[1\] …

③ 位置(1,3):KaTeX parse error: Undefined control sequence: \[ at position 3: s1\̲[̲3\] \\neq s2\[1…

④ 位置(1,4):KaTeX parse error: Undefined control sequence: \[ at position 3: s1\̲[̲4\] \\neq s2\[1…

⑤ 位置(2,1):KaTeX parse error: Undefined control sequence: \[ at position 3: s1\̲[̲1\] \\neq s2\[2…

⑥ 位置(2,2):KaTeX parse error: Undefined control sequence: \[ at position 3: s1\̲[̲2\] != s2\[2\] …

⑦ 位置(2,3):KaTeX parse error: Undefined control sequence: \[ at position 3: s1\̲[̲3\] != s2\[2\] …

⑧ 位置(2,4):KaTeX parse error: Undefined control sequence: \[ at position 3: s1\̲[̲4\] == s2\[2\] …

这个就是二维数据中,每一格的填充方式。

根据上述的一个清晰的思路,状态转移填充二位数组的代码描述:

for i in range(1, size2):

for j in range(1, size1):

if text2[i] == text1[j]:

注意这里是dp[i-1][j-1]+1

dp[i][j] = dp[i-1][j-1] + 1

else:

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

return dp[-1][-1]

好了,这个是一个全面的答案:

def longestCommonSubsequence(self, text1, text2):

size1 = len(text1)

size2 = len(text2)

1 先定义 dp 数组

dp = [[0 for _ in range(size1)] for _ in range(size2)]

2 初始化 dp 数组的第 0 行和第 0 列

if text1[0] == text2[0]:

dp[0][0] = 1

for i in range(1, size1):

dp[0][i] = 1 if dp[0][i-1] == 1 else int(text1[i] == text2[0])

for j in range(1, size2):

dp[j][0] = 1 if dp[j-1][0] == 1 else int(text2[j] == text1[0])

3 动态方程进行求解

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
(img-BnxNH0ym-1715159356241)]

[外链图片转存中…(img-4dE35aMV-1715159356242)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 24
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值