题目:带权重的最长公共子序列(Weighted Longest Common Subsequence)
字数 2892 2025-12-23 07:38:40

好的,我注意到“最长公共子序列(LCS)问题(带权重版本)”没有作为一个独立的、详细的题目在您的列表中系统地讲解过。我们就以此为题。

题目:带权重的最长公共子序列(Weighted Longest Common Subsequence)

题目描述:

给定两个字符串 text1text2,长度分别为 mn。同时,给你一个字母权重映射表 weightweight[c] 表示字符 c 的权重值(假设权重为非负整数)。两个字符串的公共子序列定义为:在两个字符串中按原顺序(不必连续)挑选出一些字符,这些字符按顺序排列起来的字符串是相同的。

带权重的最长公共子序列(WLCS) 的目标不是寻找最长的公共子序列(按字符个数),而是寻找所有公共子序列中,其组成字符的权重和最大的那个序列的权重和。

例如:

text1 = "ABCBDAB"
text2 = "BDCABA"
weight: {'A': 2, 'B': 3, 'C': 1, 'D': 4}

在这个例子中,字符串 "BCBA" 是一个公共子序列,其权重和为 3(B) + 1(C) + 3(B) + 2(A) = 9。
字符串 "BDAB" 也是一个公共子序列,其权重和为 3 + 4 + 2 + 3 = 12。
你的任务就是找到这个最大的权重和。

解题过程(区间动态规划思路):

标准的 LCS 问题是区间动态规划(或者说二维DP)的经典问题。WLCS 是其一个自然的变种。

  1. 定义状态:
    我们定义 dp[i][j] 为:考虑 text1 的前 i 个字符(即 text1[0...i-1])和 text2 的前 j 个字符(即 text2[0...j-1])时,所能得到的 最大权重和的公共子序列的权重和

    • i 的取值范围是 0m
    • j 的取值范围是 0n
    • dp[0][j] 表示 text1 为空字符串,dp[i][0] 表示 text2 为空字符串。这些情况下,公共子序列只能是空序列,权重和为 0
  2. 状态转移方程(核心):
    对于 i > 0j > 0 的情况,我们需要考虑 text1[i-1]text2[j-1] 这两个字符(注意下标偏移,i-1 对应第 i 个字符)。

    • 情况一:这两个字符相等(text1[i-1] == text2[j-1]
      这是一个很好的匹配。我们可以选择将这两个字符加入到最优的公共子序列中。
      如果加入,那么权重和就是 dp[i-1][j-1](前 i-1 和 前 j-1 个字符的最佳结果)加上这个字符的权重 weight[text1[i-1]]
      当然,我们也可以选择不加入它们,那么最优结果就来自于另外两种情况(见情况二)。由于我们要求最大值,所以取“加入”和“不加入”中的较大者。
      因此转移方程为:dp[i][j] = max(dp[i-1][j-1] + weight[text1[i-1]], dp[i-1][j], dp[i][j-1])

    • 情况二:这两个字符不相等(text1[i-1] != text2[j-1]
      那么它们不可能同时出现在一个公共子序列的同一位置。最优的公共子序列可能来自于:

      1. 不考虑 text1[i-1],只考虑 text1i-1text2j 个字符,即 dp[i-1][j]
      2. 不考虑 text2[j-1],只考虑 text1itext2j-1 个字符,即 dp[i][j-1]
        我们需要取这两者中的最大值。
        因此转移方程为:dp[i][j] = max(dp[i-1][j], dp[i][j-1])

    合并简化:
    仔细观察,情况一中的 max(dp[i-1][j-1] + weight, dp[i-1][j], dp[i][j-1]),由于 dp[i-1][j]dp[i][j-1] 可能已经包含了 dp[i-1][j-1] 的信息(dp 值单调不减),并且情况二的转移与之相同,我们可以得到一个更统一的写法,这在编程实现时更简洁:

    if text1[i-1] == text2[j-1]:
        dp[i][j] = dp[i-1][j-1] + weight[text1[i-1]]
    else:
        dp[i][j] = max(dp[i-1][j], dp[i][j-1])
    

    注意,这个简化形式在标准 LCS(求长度)中是等价的,但对于带权重的版本,这个写法也是正确的,因为它清晰地表达了:只有当字符匹配时,我们才能“赚取”这个字符的权重

  3. 初始化:
    根据定义,当其中一个字符串为空时,最大权重和为 0。

    dp[0][j] = 0, 对于所有 j 从 0 到 n
    dp[i][0] = 0, 对于所有 i 从 0 到 m
    
  4. 计算顺序与最终答案:
    由于 dp[i][j] 依赖于 dp[i-1][j-1], dp[i-1][j], dp[i][j-1],我们可以按 i1mj1n 的顺序,用双重循环来计算。
    最终,dp[m][n] 就是我们要的答案,即考虑整个 text1text2 时,带权重的公共子序列的最大权重和。

  5. 举例验证(使用题目中的例子):
    text1 = "ABCBDAB", text2 = "BDCABA", weight = {'A':2, 'B':3, 'C':1, 'D':4}

    我们构建 dp 表(8x7,包含空串行/列):

    • 初始化首行首列为 0。
    • i=1, j=1: text1[0]='A', text2[0]='B',不相等,dp[1][1] = max(dp[0][1], dp[1][0]) = max(0,0) = 0
    • i=1, j=2: 'A' != 'D', dp[1][2] = max(dp[0][2], dp[1][1]) = max(0,0) = 0
    • ... 继续计算直到匹配发生。
    • i=2, j=1: 'B' == 'B',匹配!dp[2][1] = dp[1][0] + weight['B'] = 0 + 3 = 3
    • i=3, j=2: 'C' == 'C',匹配!dp[3][2] = dp[2][1] + weight['C'] = 3 + 1 = 4
    • i=4, j=3: 'B' == 'B',匹配!dp[4][3] = dp[3][2] + weight['B'] = 4 + 3 = 7
    • ... 最终计算到 dp[7][6]

    完整计算后,dp[7][6](即 dp[m][n])的值就是我们寻找的最大权重和。你可以手动或编程验证,最终结果为 12,对应的一个最优公共子序列是 "BDAB" (权重 3+4+2+3=12) 或 "BCAB" (权重 3+1+2+3=9) 中的最大值自然是 12。

复杂度分析:

  • 时间复杂度: O(m * n),因为我们需要填充一个大小为 (m+1) * (n+1) 的 DP 表。
  • 空间复杂度: O(m * n),如果使用完整的二维数组。可以通过滚动数组优化到 O(min(m, n)),因为计算 dp[i][j] 时只依赖于当前行和前一行。
好的,我注意到“最长公共子序列(LCS)问题(带权重版本)”没有作为一个独立的、详细的题目在您的列表中系统地讲解过。我们就以此为题。 题目:带权重的最长公共子序列(Weighted Longest Common Subsequence) 题目描述: 给定两个字符串 text1 和 text2 ,长度分别为 m 和 n 。同时,给你一个字母权重映射表 weight , weight[c] 表示字符 c 的权重值(假设权重为非负整数)。两个字符串的 公共子序列 定义为:在两个字符串中按原顺序(不必连续)挑选出一些字符,这些字符按顺序排列起来的字符串是相同的。 带权重的最长公共子序列(WLCS) 的目标不是寻找最长的公共子序列(按字符个数),而是寻找所有公共子序列中,其组成字符的 权重和最大 的那个序列的权重和。 例如: 在这个例子中,字符串 "BCBA" 是一个公共子序列,其权重和为 3( B ) + 1( C ) + 3( B ) + 2( A ) = 9。 字符串 "BDAB" 也是一个公共子序列,其权重和为 3 + 4 + 2 + 3 = 12。 你的任务就是找到这个最大的权重和。 解题过程(区间动态规划思路): 标准的 LCS 问题是区间动态规划(或者说二维DP)的经典问题。WLCS 是其一个自然的变种。 定义状态: 我们定义 dp[i][j] 为:考虑 text1 的前 i 个字符(即 text1[0...i-1] )和 text2 的前 j 个字符(即 text2[0...j-1] )时,所能得到的 最大权重和的公共子序列的权重和 。 i 的取值范围是 0 到 m 。 j 的取值范围是 0 到 n 。 dp[0][j] 表示 text1 为空字符串, dp[i][0] 表示 text2 为空字符串。这些情况下,公共子序列只能是空序列,权重和为 0 。 状态转移方程(核心): 对于 i > 0 且 j > 0 的情况,我们需要考虑 text1[i-1] 和 text2[j-1] 这两个字符(注意下标偏移, i-1 对应第 i 个字符)。 情况一:这两个字符相等( text1[i-1] == text2[j-1] ) 这是一个很好的匹配。我们可以选择将这两个字符加入到最优的公共子序列中。 如果加入,那么权重和就是 dp[i-1][j-1] (前 i-1 和 前 j-1 个字符的最佳结果)加上这个字符的权重 weight[text1[i-1]] 。 当然,我们也可以选择不加入它们,那么最优结果就来自于另外两种情况(见情况二)。由于我们要求最大值,所以取“加入”和“不加入”中的较大者。 因此转移方程为: dp[i][j] = max(dp[i-1][j-1] + weight[text1[i-1]], dp[i-1][j], dp[i][j-1]) 情况二:这两个字符不相等( text1[i-1] != text2[j-1] ) 那么它们不可能同时出现在一个公共子序列的同一位置。最优的公共子序列可能来自于: 不考虑 text1[i-1] ,只考虑 text1 前 i-1 和 text2 前 j 个字符,即 dp[i-1][j] 。 不考虑 text2[j-1] ,只考虑 text1 前 i 和 text2 前 j-1 个字符,即 dp[i][j-1] 。 我们需要取这两者中的最大值。 因此转移方程为: dp[i][j] = max(dp[i-1][j], dp[i][j-1]) 合并简化: 仔细观察,情况一中的 max(dp[i-1][j-1] + weight, dp[i-1][j], dp[i][j-1]) ,由于 dp[i-1][j] 和 dp[i][j-1] 可能已经包含了 dp[i-1][j-1] 的信息( dp 值单调不减),并且情况二的转移与之相同,我们可以得到一个更统一的写法,这在编程实现时更简洁: 注意,这个简化形式在标准 LCS(求长度)中是等价的,但对于带权重的版本,这个写法也是正确的,因为它清晰地表达了: 只有当字符匹配时,我们才能“赚取”这个字符的权重 。 初始化: 根据定义,当其中一个字符串为空时,最大权重和为 0。 计算顺序与最终答案: 由于 dp[i][j] 依赖于 dp[i-1][j-1] , dp[i-1][j] , dp[i][j-1] ,我们可以按 i 从 1 到 m , j 从 1 到 n 的顺序,用双重循环来计算。 最终, dp[m][n] 就是我们要的答案,即考虑整个 text1 和 text2 时,带权重的公共子序列的最大权重和。 举例验证(使用题目中的例子): text1 = "ABCBDAB" , text2 = "BDCABA" , weight = {'A':2, 'B':3, 'C':1, 'D':4} 我们构建 dp 表(8x7,包含空串行/列): 初始化首行首列为 0。 i=1, j=1 : text1[0]='A' , text2[0]='B' ,不相等, dp[1][1] = max(dp[0][1], dp[1][0]) = max(0,0) = 0 i=1, j=2 : 'A' != 'D' , dp[1][2] = max(dp[0][2], dp[1][1]) = max(0,0) = 0 ... 继续计算直到匹配发生。 i=2, j=1 : 'B' == 'B' ,匹配! dp[2][1] = dp[1][0] + weight['B'] = 0 + 3 = 3 i=3, j=2 : 'C' == 'C' ,匹配! dp[3][2] = dp[2][1] + weight['C'] = 3 + 1 = 4 i=4, j=3 : 'B' == 'B' ,匹配! dp[4][3] = dp[3][2] + weight['B'] = 4 + 3 = 7 ... 最终计算到 dp[7][6] 。 完整计算后, dp[7][6] (即 dp[m][n] )的值就是我们寻找的最大权重和。你可以手动或编程验证,最终结果为 12 ,对应的一个最优公共子序列是 "BDAB" (权重 3+4+2+3=12) 或 "BCAB" (权重 3+1+2+3=9) 中的最大值自然是 12。 复杂度分析: 时间复杂度: O(m * n),因为我们需要填充一个大小为 (m+1) * (n+1) 的 DP 表。 空间复杂度: O(m * n),如果使用完整的二维数组。可以通过滚动数组优化到 O(min(m, n)),因为计算 dp[i][j] 时只依赖于当前行和前一行。