好的,我注意到“最长公共子序列(LCS)问题(带权重版本)”没有作为一个独立的、详细的题目在您的列表中系统地讲解过。我们就以此为题。
题目:带权重的最长公共子序列(Weighted Longest Common Subsequence)
题目描述:
给定两个字符串 text1 和 text2,长度分别为 m 和 n。同时,给你一个字母权重映射表 weight,weight[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 是其一个自然的变种。
-
定义状态:
我们定义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值单调不减),并且情况二的转移与之相同,我们可以得到一个更统一的写法,这在编程实现时更简洁: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(求长度)中是等价的,但对于带权重的版本,这个写法也是正确的,因为它清晰地表达了:只有当字符匹配时,我们才能“赚取”这个字符的权重。
-
-
初始化:
根据定义,当其中一个字符串为空时,最大权重和为 0。dp[0][j] = 0, 对于所有 j 从 0 到 n dp[i][0] = 0, 对于所有 i 从 0 到 m -
计算顺序与最终答案:
由于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) = 0i=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 = 3i=3, j=2:'C' == 'C',匹配!dp[3][2] = dp[2][1] + weight['C'] = 3 + 1 = 4i=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]时只依赖于当前行和前一行。