0x3f第14天 最长公共子序列

1.子数组/子串是连续的 子序列不一定是连续的 abcde中ace就是子序列

2.给定两个字符串,求两个的最长公共子序列,dfs(i,j)定义:序列1的前i个字母和序列2的前j个字母 的最长公共子序列

下一个问题:序列1前i-1个字母和序列二前j-1个字母

序列1前i个字母和序列二前j-1个字母

序列1前i-1个字母和序列二前j个字母

可以简化:

dfs(i,j) = dfs(i-1,j-1)+1 s[i]=t[j]

dfs(i,j) = max(dfs(i-1,j),dfs(i,j-1)) s[i]≠t[j]

最长公共子序列

1.回溯法

时间复杂度 nm 空间复杂度 nm

复制代码
class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        n = len(text1)
        m = len(text2)

        @cache
        def dfs(i,j):
            if i<0 or j<0:
                return 0
            if text1[i]==text2[j]:
                return dfs(i-1,j-1)+1
            else:
                return max(dfs(i-1,j),dfs(i,j-1))
        return dfs(n-1,m-1)

2.递推

先得到递推公式

f[i][j] = f[i-1][j-1]+1 s[i]=t[j]

max(f[i-1][j],f[i][j-1] s[i]≠t[j]

复制代码
        f = [[0]*(m+1)for _ in range(n+1)]
        for i,x in enumerate(text1):
            for j,y in enumerate(text2):
                if x==y:
                    f[i+1][j+1] = f[i][j]+1
                else:
                    f[i+1][j+1] = max(f[i][j+1],f[i+1][j])
        return f[n][m]

3.空间优化

可以发现更新f[i+1][j+1]需要三个值,左边,上面,左上,但左上的数据会被覆盖,引入pre变量

pre = f[0] 不能写在循环外面,核心原因是:进入 j 循环后,pre 会逐步保存「上一个 j 位置的旧值」,精准匹配二维 DP 中dp[i-1][j-1]的语义。

空间复杂度 m

复制代码
        f = [0]*(m+1)
        
        for i,x in enumerate(text1):
            pre = f[0]
            for j,y in enumerate(text2):
                tmp = f[j+1]
                if x==y:
                    f[j+1] = pre+1
                else:
                    f[j+1] = max(f[j+1],f[j])
                pre = tmp
        return f[m]

编辑距离

为什么叫编辑距离:

"编辑":指对字符串的修改操作(插入、删除、替换是文本编辑中最基础的三种操作)

距离: 操作次数越少,差异越小,距离越近

dfs(i, j)的定义 表示将 s[0..i](s 的前 i+1 个字符)转换为 t[0..j](t 的前 j+1 个字符)所需的最少操作数

dfs(i-1, j) + 1 → 对应「删除操作」

含义:先将 s[0..i-1] 转换为 t[0..j](操作数是 dfs(i-1, j)),再删除 s[i](+1 次操作)

dfs(i, j-1) + 1 → 对应「插入操作」

  • 含义:先将 s[0..i] 转换为 t[0..j-1](操作数是 dfs(i, j-1)),再插入 t[j] 到 s 中(+1 次操作)。

dfs(i-1, j-1) + 1 → 对应「替换操作」

含义:先将 s[0..i-1] 转换为 t[0..j-1](操作数是 dfs(i-1, j-1)),再s[i] 替换为 t[j](+1 次操作)

而如果 s[i] == t[j],则不需要任何操作,直接继承 dfs(i-1, j-1) 的结果(这是 "无操作" 的情况)。

1.回溯法

1.回溯公式:

dfs(i,j) = dfs(i-1,j-1) if s[i] == t[j]

dfs(i,j) = min(dfs(i,j-1),dfs(i-1,j),dfs(i-1,j-1)) +1 if s[i] ≠ t[j]

插入 删除 替换

2.边界条件:if i<0:return j+1 把j全加一遍,就相同了

if j<0:return i+1 把i全删了,就相同了

复制代码
class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        n = len(word1)
        m = len(word2)
        @cache
        def dfs(i,j):
            if i<0:return j+1
            if j<0:return i+1
            if word1[i]==word2[j]:
                return dfs(i-1,j-1)
            else:
                return min(dfs(i,j-1),dfs(i-1,j),dfs(i-1,j-1))+1
        return dfs(n-1,m-1)

2.递推:

我卡住的地方:

if i<0:return j+1

if j<0:return i+1 怎么转换到二维数组里

首先 dfs 的定义是将 text1 前 i 转换为 text2 前 j 最少需要的操作次数

DP 的f[0][j]:对应递归的i=-1,此时 DP 的j对应递归的j-1 → 应返回(j-1)+1 = j(而非j+1

综上:i<0 对应的是 f[0][j] 数组的第一行 j+1 在这是 j

j<0对应的是 f[i][0]数组的第一列 i+1在这是i

所以边界条件:

for j in range(m+1):

f[0][j] = j

for i in range(n+1):

f[i][0] = i

复制代码
        f = [[0]*(m+1)for _ in range(n+1)]
        for j in range(m+1):
            f[0][j] = j
        for i in range(n+1):
            f[i][0] = i
        for i,x in enumerate(word1):
            for j,y in enumerate(word2):
                if x==y:
                    f[i+1][j+1] = f[i][j]
                else:
                    f[i+1][j+1] = min(f[i+1][j],f[i][j+1],f[i][j])+1
        return f[n][m]

3.空间优化

和最长公子序列不同的是,这个题的二维数组的第一行和第一列是动态的,而最长公子序列的第一行和第一列都是0,所以这个题要考虑的更多

1.一维数组的创建:

f = [j for j in range(m+1)] 并不是简单的创了一个全是0 的数组

2.pre = f[0] f[0] = i+1 pre记录旧值,f[0] = i+1对应的是

dfs的 for i in range(n+1):

f[i][0] = i的操作

综合起来达到动态的f[0]的效果

复制代码
        f = [j for j in range(m+1)]
        for i,x in enumerate(word1):
            pre = f[0]
            f[0] = i+1
            for j,y in enumerate(word2):
                temp = f[j+1]
                if x==y:
                    f[j+1] = pre
                else:
                    f[j+1] = min(f[j],f[j+1],pre) +1
                pre=temp

        return f[m]

最长递增子序列

1.回溯法:

若用常见的选或不选思路进行回溯,会递归遍历所有可能的子序列,比如数组长度为 n 时,子序列总数是 2ⁿ

修改思路,dfs(i)专注于 "以 i 结尾" 的子序列长度

外部遍历所有i,是为了覆盖 "以任意元素结尾" 的所有可能,从而得到全局最长长度。

此时dfs(i)的定义:以nums[i]结尾的最长递增子序列长度

外边再遍历一遍所有结尾情况,就能得出答案了

回溯公式:

dfs(i) = max {dfs(j)} +1 j<i and nums[j]<nums[i]

回溯代码:

def dfs(i):

temp = 0

for j in range(i):

if nums[j] < nums[i]:

temp = max(temp,dfs[j])

return temp+1

复制代码
class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        n = len(nums)
        ans = 0
        @cache
        def dfs(i):
            temp=0
            for j in range(i):
                if nums[j]<nums[i]:
                    temp = max(temp,dfs(j))
            return temp+1
        
        for i in range(n):
            ans = max(ans,dfs(i))
        return ans

2.递推

递推公式:

f[i] = max{f(j)} +1     j<i and nums[j]<nums[i]

复制代码
        f = [0]*n
        for i in range(n):
            for j in range(i):
                if nums[j]<nums[i]:
                    f[i] = max(f[i],f[j])
            f[i] += 1
        return max(f)
相关推荐
spssau2 小时前
正交试验设计全解析:从正交表生成到极差与方差分析
数据库·算法·机器学习
minhuan2 小时前
大模型应用:不减性能只减负担:大模型稀疏化技术全景与实践.36
大数据·人工智能·算法
爱学大树锯2 小时前
592 · 查找和替换模式
算法
爱学大树锯2 小时前
775 · 回文对
算法
girl-07263 小时前
2025.12.26代码分析
数据结构·算法
大罗辑3 小时前
2026软考备考刷题:软件设计师经典100题(5)含详细解析
算法·软考·软件设计师·2026软考·软设备考
咕噜企业分发小米3 小时前
阿里云Milvus支持哪些向量检索算法?
算法·机器学习·milvus
蒙奇D索大3 小时前
【数据结构】排序算法精讲|折半插入排序全解:高效优化、性能对比、实战剖析
数据结构·学习·考研·算法·排序算法·改行学it
汽车仪器仪表相关领域3 小时前
ZDT-III 通用电机测试系统
数据库·算法·单元测试·压力测试·可用性测试