线性动态规划:两个字符串的最短公共超序列长度 (Shortest Common Supersequence - Length)
字数 3099 2025-12-21 22:09:20

好的,我现在随机为你挑选一个线性动态规划领域中,且不在你已提供列表里的算法题目。

线性动态规划:两个字符串的最短公共超序列长度 (Shortest Common Supersequence - Length)


题目描述

给你两个字符串 text1text2,请你找出一个最短的字符串,使得 text1text2 都是这个字符串的 子序列。返回这个最短字符串的 长度

子序列定义:一个字符串可以通过删除原字符串某些字符(也可以不删除)而不改变剩余字符的相对顺序得到。例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是。

示例 1

  • 输入:text1 = "abac", text2 = "cab"
  • 输出:5
  • 解释:最短公共超序列之一是 "cabac""abac""c a b a c" 的子序列(标记下划线)。"cab" 也是 "c a b a c" 的子序列。长度为5。

示例 2

  • 输入:text1 = "aaaaaaaa", text2 = "aaaaaaaa"
  • 输出:8
  • 解释:最短公共超序列就是它本身。

解题过程循序渐进讲解

我们的目标是求最短公共超序列(SCS)的长度。这个问题和最长公共子序列(LCS)有着非常直接和优美的关系。

第一步:将问题转化为已知问题

让我们思考一下 text1(长度为 n)和 text2(长度为 m)以及它们的最长公共子序列 LCS(长度为 l)。

  1. 超序列与子序列的关系:一个超序列必须包含 text1text2 的所有字符,同时保持各自原有的顺序。
  2. 重叠部分:如果 text1text2 有公共的字符(按照顺序),那么在构造超序列时,这些公共字符只需要出现一次,就可以同时满足是 text1text2 的子序列的要求。
  3. 核心观察:这些“只需要出现一次”的字符,正是 text1text2最长公共子序列 (LCS)

第二步:推导长度公式

我们可以这样构建最短公共超序列:

  • 先写出 text1text2LCS
  • 对于 text1不属于 LCS 的字符,我们必须按顺序将它们插入到超序列中 LCS 的对应位置之间或两端。
  • 对于 text2不属于 LCS 的字符,我们也必须按顺序将它们插入。

长度计算

  • text1 的长度是 n,其中 l 个字符已经在 LCS 里,所以需要额外插入 (n - l) 个来自 text1 的独有字符。
  • text2 的长度是 m,其中 l 个字符已经在 LCS 里,所以需要额外插入 (m - l) 个来自 text2 的独有字符。
  • 最后,再加上 LCS 本身的 l 个字符。

因此,最短公共超序列的长度 = (n - l) + (m - l) + l = n + m - l

结论SCS长度 = text1长度 + text2长度 - LCS长度

第三步:设计动态规划状态

既然我们已经知道关键在于求 LCS长度,而这是一个经典的动态规划问题。

我们定义 dp[i][j]:表示 text1 的前 i 个字符(下标 0 到 i-1)和 text2 的前 j 个字符(下标 0 到 j-1)的 最长公共子序列的长度

  • i 的取值范围是 [0, n]
  • j 的取值范围是 [0, m]
  • dp[0][j] 表示 text1 取空字符串,所以 LCS 长度为 0。
  • dp[i][0] 同理。

我们的最终目标是 dp[n][m],即两个完整字符串的 LCS 长度 l

第四步:建立状态转移方程

我们考虑 text1[i-1]text2[j-1] 这两个字符(因为下标从0开始)。

  1. 如果两个字符相等

    • 这个字符必然是 LCS 的一部分。
    • 那么,text1i-1 个字符和 text2j-1 个字符的 LCS 长度加上这个字符,就构成了新的 LCS。
    • 所以,dp[i][j] = dp[i-1][j-1] + 1
  2. 如果两个字符不相等

    • 那么,text1[i-1]text2[j-1] 不可能同时出现在当前的 LCS 中。
    • 我们有两种选择:
      a) 不考虑 text1[i-1],看 text1i-1 个字符和 text2j 个字符的 LCS。
      b) 不考虑 text2[j-1],看 text1i 个字符和 text2j-1 个字符的 LCS。
    • 当前的 LCS 长度应该是这两种选择中的最大值(因为我们要找最长的)。
    • 所以,dp[i][j] = max(dp[i-1][j], dp[i][j-1])

转移方程总结

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[0][j] = 0,空字符串和任何字符串的 LCS 长度为 0。
  • dp[i][0] = 0,同理。

第六步:计算顺序与最终答案

我们从小规模问题向大规模问题计算:

  • 使用两层循环,i 从 1 到 nj 从 1 到 m
  • 计算完整个 dp 表后,l = dp[n][m]
  • 最终答案:SCS长度 = n + m - l

第七步:用示例验证

text1 = "abac" (n=4), text2 = "cab" (m=3) 为例。

  1. 构建 dp 表(5行 x 4列,包含空串情况):

    dp[i][j] 表 (i行,j列):
             ''  'c'  'a'  'b'
    ''       0    0    0    0
    'a'      0    0    1    1
    'b'      0    0    1    2
    'a'      0    0    1    2
    'c'      0    1    1    2
    

    计算过程(逐步):

    • i=1, j=1'a' != 'c'dp[1][1] = max(dp[0][1], dp[1][0]) = max(0, 0) = 0
    • i=1, j=2'a' == 'a'dp[1][2] = dp[0][1] + 1 = 0 + 1 = 1
    • i=2, j=3'b' == 'b'dp[2][3] = dp[1][2] + 1 = 1 + 1 = 2
    • i=4, j=1'c' == 'c'dp[4][1] = dp[3][0] + 1 = 0 + 1 = 1
    • 等等。
  2. dp[4][3] = 2,所以 l = 2

  3. 最短公共超序列长度 = n + m - l = 4 + 3 - 2 = 5。与示例一致。

第八步:复杂度分析

  • 时间复杂度:我们需要填充一个 (n+1) x (m+1)dp 表,每次操作是常数时间。所以总时间复杂度为 O(n * m)
  • 空间复杂度:如果使用完整的二维数组,是 O(n * m)。可以观察到状态转移只依赖于上一行和当前行的左边,所以可以优化到 O(min(n, m))

核心要点总结

这道题的巧妙之处在于,它将“最短公共超序列”问题,转化为了经典的“最长公共子序列”问题

核心公式
最短公共超序列长度 = 字符串A长度 + 字符串B长度 - 最长公共子序列长度

你只需要熟练解决 LCS 的动态规划,就能轻松解决 SCS 的长度问题。如果需要输出具体的超序列字符串,则需要在动态规划过程中记录路径(使用额外的数组存储选择方向),然后在回溯时根据 dp 值和字符相等关系来构造。

好的,我现在随机为你挑选一个线性动态规划领域中,且不在你已提供列表里的算法题目。 线性动态规划:两个字符串的最短公共超序列长度 (Shortest Common Supersequence - Length) 题目描述 给你两个字符串 text1 和 text2 ,请你找出一个 最短的字符串 ,使得 text1 和 text2 都是这个字符串的 子序列 。返回这个最短字符串的 长度 。 子序列定义 :一个字符串可以通过删除原字符串某些字符(也可以不删除)而不改变剩余字符的相对顺序得到。例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是。 示例 1 : 输入: text1 = "abac" , text2 = "cab" 输出: 5 解释:最短公共超序列之一是 "cabac" 。 "abac" 是 "c a b a c" 的子序列(标记下划线)。 "cab" 也是 "c a b a c" 的子序列。长度为5。 示例 2 : 输入: text1 = "aaaaaaaa" , text2 = "aaaaaaaa" 输出: 8 解释:最短公共超序列就是它本身。 解题过程循序渐进讲解 我们的目标是求最短公共超序列(SCS)的长度。这个问题和最长公共子序列(LCS)有着非常直接和优美的关系。 第一步:将问题转化为已知问题 让我们思考一下 text1 (长度为 n )和 text2 (长度为 m )以及它们的最长公共子序列 LCS (长度为 l )。 超序列与子序列的关系 :一个超序列必须包含 text1 和 text2 的所有字符,同时保持各自原有的顺序。 重叠部分 :如果 text1 和 text2 有公共的字符(按照顺序),那么在构造超序列时,这些公共字符只需要 出现一次 ,就可以同时满足是 text1 和 text2 的子序列的要求。 核心观察 :这些“只需要出现一次”的字符,正是 text1 和 text2 的 最长公共子序列 (LCS) 。 第二步:推导长度公式 我们可以这样构建最短公共超序列: 先写出 text1 和 text2 的 LCS 。 对于 text1 中 不属于 LCS 的字符,我们必须按顺序将它们插入到超序列中 LCS 的对应位置之间或两端。 对于 text2 中 不属于 LCS 的字符,我们也必须按顺序将它们插入。 长度计算 : text1 的长度是 n ,其中 l 个字符已经在 LCS 里,所以需要额外插入 (n - l) 个来自 text1 的独有字符。 text2 的长度是 m ,其中 l 个字符已经在 LCS 里,所以需要额外插入 (m - l) 个来自 text2 的独有字符。 最后,再加上 LCS 本身的 l 个字符。 因此, 最短公共超序列的长度 = (n - l) + (m - l) + l = n + m - l 。 结论 : SCS长度 = text1长度 + text2长度 - LCS长度 。 第三步:设计动态规划状态 既然我们已经知道关键在于求 LCS长度 ,而这是一个经典的动态规划问题。 我们定义 dp[i][j] :表示 text1 的前 i 个字符(下标 0 到 i-1)和 text2 的前 j 个字符(下标 0 到 j-1)的 最长公共子序列的长度 。 i 的取值范围是 [0, n] j 的取值范围是 [0, m] dp[0][j] 表示 text1 取空字符串,所以 LCS 长度为 0。 dp[i][0] 同理。 我们的最终目标是 dp[n][m] ,即两个完整字符串的 LCS 长度 l 。 第四步:建立状态转移方程 我们考虑 text1[i-1] 和 text2[j-1] 这两个字符(因为下标从0开始)。 如果两个字符相等 : 这个字符必然是 LCS 的一部分。 那么, text1 前 i-1 个字符和 text2 前 j-1 个字符的 LCS 长度加上这个字符,就构成了新的 LCS。 所以, dp[i][j] = dp[i-1][j-1] + 1 。 如果两个字符不相等 : 那么, text1[i-1] 和 text2[j-1] 不可能同时 出现在当前的 LCS 中。 我们有两种选择: a) 不考虑 text1[i-1] ,看 text1 前 i-1 个字符和 text2 前 j 个字符的 LCS。 b) 不考虑 text2[j-1] ,看 text1 前 i 个字符和 text2 前 j-1 个字符的 LCS。 当前的 LCS 长度应该是这两种选择中的最大值(因为我们要找最长的)。 所以, dp[i][j] = max(dp[i-1][j], dp[i][j-1]) 。 转移方程总结 : 第五步:确定初始条件 dp[0][j] = 0 ,空字符串和任何字符串的 LCS 长度为 0。 dp[i][0] = 0 ,同理。 第六步:计算顺序与最终答案 我们从小规模问题向大规模问题计算: 使用两层循环, i 从 1 到 n , j 从 1 到 m 。 计算完整个 dp 表后, l = dp[n][m] 。 最终答案: SCS长度 = n + m - l 。 第七步:用示例验证 以 text1 = "abac" (n=4) , text2 = "cab" (m=3) 为例。 构建 dp 表(5行 x 4列,包含空串情况): 计算过程(逐步): i=1, j=1 : 'a' != 'c' , dp[1][1] = max(dp[0][1], dp[1][0]) = max(0, 0) = 0 i=1, j=2 : 'a' == 'a' , dp[1][2] = dp[0][1] + 1 = 0 + 1 = 1 i=2, j=3 : 'b' == 'b' , dp[2][3] = dp[1][2] + 1 = 1 + 1 = 2 i=4, j=1 : 'c' == 'c' , dp[4][1] = dp[3][0] + 1 = 0 + 1 = 1 等等。 dp[4][3] = 2 ,所以 l = 2 。 最短公共超序列长度 = n + m - l = 4 + 3 - 2 = 5 。与示例一致。 第八步:复杂度分析 时间复杂度 :我们需要填充一个 (n+1) x (m+1) 的 dp 表,每次操作是常数时间。所以总时间复杂度为 O(n * m) 。 空间复杂度 :如果使用完整的二维数组,是 O(n * m) 。可以观察到状态转移只依赖于上一行和当前行的左边,所以可以优化到 O(min(n, m)) 。 核心要点总结 这道题的巧妙之处在于,它 将“最短公共超序列”问题,转化为了经典的“最长公共子序列”问题 。 核心公式 : 最短公共超序列长度 = 字符串A长度 + 字符串B长度 - 最长公共子序列长度 你只需要熟练解决 LCS 的动态规划,就能轻松解决 SCS 的长度问题。如果需要输出具体的超序列字符串,则需要在动态规划过程中记录路径(使用额外的数组存储选择方向),然后在回溯时根据 dp 值和字符相等关系来构造。