区间动态规划例题:最小代价合并字符串形成目标字符串
字数 3768 2025-12-13 05:47:44

区间动态规划例题:最小代价合并字符串形成目标字符串

问题描述

我们有两个字符串 sourcetarget,它们的长度分别为 nm。我们希望通过一系列操作将 source 转换为 target。允许的操作有两种:

  1. 插入字符:在当前字符串的任意位置插入任意字符,每次插入的成本为 insertCost
  2. 删除字符:删除当前字符串中的任意字符,每次删除的成本为 deleteCost

我们的目标是找到将 source 完全转换为 target 的最小总成本。

输入

  • 字符串 sourcetarget,长度分别为 nm(长度范围通常在 1 到 500 之间)。
  • 插入成本 insertCost(整数,大于等于 0)。
  • 删除成本 deleteCost(整数,大于等于 0)。

输出:一个整数,表示最小总成本。

示例

  • source = "abcd", target = "acd", insertCost = 1, deleteCost = 1。
    最小成本为 2:删除 'b'(成本1),然后在 'c' 后插入 'd'(成本1)?不,这里其实更简单:只需删除 'b' 即可将 "abcd" 变成 "acd",但 "acd" 中 'd' 的位置?让我们仔细分析:source = "a b c d",target = "a c d"。对应关系是:
    source[0]='a' -> target[0]='a' (匹配)
    source[1]='b' -> 需要删除 (成本1)
    source[2]='c' -> target[1]='c' (匹配)
    source[3]='d' -> target[2]='d' (匹配)
    总成本 = 1。
    但如果考虑另一种情况,例如 source = "abc", target = "ac", insertCost = 5, deleteCost = 3。
    方案1:删除 'b'(成本3),得到 "ac"。总成本=3。
    方案2:删除 'a'(成本3),删除 'b'(成本3),删除 'c'(成本3),然后插入 'a'(成本5),插入 'c'(成本5),总成本远大于3。显然方案1最优。
    但问题描述中只允许插入和删除,不允许替换。因此,这本质上是求两个字符串的“编辑距离”,但只允许插入和删除操作(不允许替换)。

问题分析

这是一个经典的编辑距离问题的变种,通常用动态规划解决。但由于我们只允许插入和删除,状态转移会略有不同。我们定义 dp[i][j] 为将 source 的前 i 个字符转换为 target 的前 j 个字符所需的最小成本。

状态定义

dp[i][j] 表示将 source 的前 i 个字符(即 source[0..i-1])转换为 target 的前 j 个字符(即 target[0..j-1])的最小成本。

  • i 范围:0 到 n。
  • j 范围:0 到 m。

初始状态

  1. dp[0][0] = 0:两个空字符串之间转换不需要任何操作。
  2. 第一行 dp[0][j]:将空字符串转换为 target 的前 j 个字符,只能进行 j 次插入操作,所以 dp[0][j] = j * insertCost
  3. 第一列 dp[i][0]:将 source 的前 i 个字符转换为空字符串,只能进行 i 次删除操作,所以 dp[i][0] = i * deleteCost

状态转移方程

考虑 dp[i][j] 如何从之前的状态转移而来。我们关注最后一个字符:

  • 如果 source[i-1] == target[j-1],那么最后一个字符已经匹配,不需要额外操作,直接从前面的状态转移:dp[i][j] = dp[i-1][j-1]

  • 如果 source[i-1] != target[j-1],我们有三种选择(但实际上只允许插入和删除,不允许直接替换,所以“替换”操作需要用一次删除加一次插入来模拟):

    1. 删除 source[i-1]:这意味着我们先处理 source 的前 i-1 个字符到 target 的前 j 个字符,然后删除 source[i-1]。成本为 dp[i-1][j] + deleteCost
    2. 插入 target[j-1]:这意味着我们先处理 source 的前 i 个字符到 target 的前 j-1 个字符,然后插入 target[j-1]。成本为 dp[i][j-1] + insertCost

    注意:我们不允许直接替换,所以没有 dp[i-1][j-1] + replaceCost 的选项。但如果我们允许替换,这里就会加上替换成本。本题只允许插入和删除,所以不匹配时,要么删除多余的字符,要么插入缺失的字符。

    因此,对于不匹配的情况,转移方程为:
    dp[i][j] = min(dp[i-1][j] + deleteCost, dp[i][j-1] + insertCost)

综合起来:

\[dp[i][j] = \begin{cases} dp[i-1][j-1] & \text{if } source[i-1] == target[j-1] \\ \min(dp[i-1][j] + deleteCost, dp[i][j-1] + insertCost) & \text{otherwise} \end{cases} \]

计算顺序

由于 dp[i][j] 依赖于 dp[i-1][j-1]dp[i-1][j]dp[i][j-1],我们可以按行或按列顺序计算,通常使用双重循环:

  • 外层循环 i 从 0 到 n。
  • 内层循环 j 从 0 到 m。

注意初始状态已经定义了 i=0j=0 的情况,所以循环从 i=1j=1 开始。

最终答案

最终答案为 dp[n][m],即将整个 source 转换为整个 target 的最小成本。

示例演算

以 source = "abc", target = "ac", insertCost = 5, deleteCost = 3 为例。

初始化:

  • dp[0][0] = 0
  • dp[0][1] = 1 * 5 = 5dp[0][2] = 2 * 5 = 10
  • dp[1][0] = 1 * 3 = 3dp[2][0] = 2 * 3 = 6dp[3][0] = 3 * 3 = 9

计算 dp[1][1]:比较 source[0]='a' 和 target[0]='a',匹配,所以 dp[1][1] = dp[0][0] = 0

dp[1][2]:source[0]='a',target[1]='c',不匹配。

  • 删除 'a':dp[0][2] + 3 = 10 + 3 = 13
  • 插入 'c':dp[1][1] + 5 = 0 + 5 = 5
    取 min = 5。

dp[2][1]:source[1]='b',target[0]='a',不匹配。

  • 删除 'b':dp[1][1] + 3 = 0 + 3 = 3
  • 插入 'a':dp[2][0] + 5 = 6 + 5 = 11
    取 min = 3。

dp[2][2]:source[1]='b',target[1]='c',不匹配。

  • 删除 'b':dp[1][2] + 3 = 5 + 3 = 8
  • 插入 'c':dp[2][1] + 5 = 3 + 5 = 8
    取 min = 8。

dp[3][1]:source[2]='c',target[0]='a',不匹配。

  • 删除 'c':dp[2][1] + 3 = 3 + 3 = 6
  • 插入 'a':dp[3][0] + 5 = 9 + 5 = 14
    取 min = 6。

dp[3][2]:source[2]='c',target[1]='c',匹配!所以 dp[3][2] = dp[2][1] = 3

最终 dp[3][2] = 3,即最小成本为 3(删除 'b')。

时间复杂度与空间复杂度

  • 时间复杂度:O(n * m),因为需要填充一个 (n+1) x (m+1) 的二维 DP 表。
  • 空间复杂度:O(n * m) 用于存储 DP 表。可以优化到 O(min(n, m)) 使用滚动数组,但通常题目中 n 和 m 在 500 以内,直接二维数组即可。

总结

这个问题是编辑距离的简化版本,只允许插入和删除操作。通过定义 dp[i][j] 并合理考虑状态转移,我们可以高效地求出最小成本。关键点在于理解:当字符匹配时直接继承前状态;不匹配时,选择删除当前源字符或插入当前目标字符中成本较小的方案。

区间动态规划例题:最小代价合并字符串形成目标字符串 问题描述 我们有两个字符串 source 和 target ,它们的长度分别为 n 和 m 。我们希望通过一系列操作将 source 转换为 target 。允许的操作有两种: 插入字符 :在当前字符串的任意位置插入任意字符,每次插入的成本为 insertCost 。 删除字符 :删除当前字符串中的任意字符,每次删除的成本为 deleteCost 。 我们的目标是找到将 source 完全转换为 target 的最小总成本。 输入 : 字符串 source 和 target ,长度分别为 n 和 m (长度范围通常在 1 到 500 之间)。 插入成本 insertCost (整数,大于等于 0)。 删除成本 deleteCost (整数,大于等于 0)。 输出 :一个整数,表示最小总成本。 示例 : source = "abcd", target = "acd", insertCost = 1, deleteCost = 1。 最小成本为 2:删除 'b'(成本1),然后在 'c' 后插入 'd'(成本1)?不,这里其实更简单:只需删除 'b' 即可将 "abcd" 变成 "acd",但 "acd" 中 'd' 的位置?让我们仔细分析:source = "a b c d",target = "a c d"。对应关系是: source[ 0]='a' -> target[ 0 ]='a' (匹配) source[ 1 ]='b' -> 需要删除 (成本1) source[ 2]='c' -> target[ 1 ]='c' (匹配) source[ 3]='d' -> target[ 2 ]='d' (匹配) 总成本 = 1。 但如果考虑另一种情况,例如 source = "abc", target = "ac", insertCost = 5, deleteCost = 3。 方案1:删除 'b'(成本3),得到 "ac"。总成本=3。 方案2:删除 'a'(成本3),删除 'b'(成本3),删除 'c'(成本3),然后插入 'a'(成本5),插入 'c'(成本5),总成本远大于3。显然方案1最优。 但问题描述中只允许插入和删除,不允许替换。因此,这本质上是求两个字符串的“编辑距离”,但只允许插入和删除操作(不允许替换)。 问题分析 这是一个经典的编辑距离问题的变种,通常用动态规划解决。但由于我们只允许插入和删除,状态转移会略有不同。我们定义 dp[i][j] 为将 source 的前 i 个字符转换为 target 的前 j 个字符所需的最小成本。 状态定义 设 dp[i][j] 表示将 source 的前 i 个字符(即 source[0..i-1] )转换为 target 的前 j 个字符(即 target[0..j-1] )的最小成本。 i 范围:0 到 n。 j 范围:0 到 m。 初始状态 dp[0][0] = 0 :两个空字符串之间转换不需要任何操作。 第一行 dp[0][j] :将空字符串转换为 target 的前 j 个字符,只能进行 j 次插入操作,所以 dp[0][j] = j * insertCost 。 第一列 dp[i][0] :将 source 的前 i 个字符转换为空字符串,只能进行 i 次删除操作,所以 dp[i][0] = i * deleteCost 。 状态转移方程 考虑 dp[i][j] 如何从之前的状态转移而来。我们关注最后一个字符: 如果 source[i-1] == target[j-1] ,那么最后一个字符已经匹配,不需要额外操作,直接从前面的状态转移: dp[i][j] = dp[i-1][j-1] 。 如果 source[i-1] != target[j-1] ,我们有三种选择(但实际上只允许插入和删除,不允许直接替换,所以“替换”操作需要用一次删除加一次插入来模拟): 删除 source[i-1] :这意味着我们先处理 source 的前 i-1 个字符到 target 的前 j 个字符,然后删除 source[i-1] 。成本为 dp[i-1][j] + deleteCost 。 插入 target[j-1] :这意味着我们先处理 source 的前 i 个字符到 target 的前 j-1 个字符,然后插入 target[j-1] 。成本为 dp[i][j-1] + insertCost 。 注意:我们不允许直接替换,所以没有 dp[i-1][j-1] + replaceCost 的选项。但如果我们允许替换,这里就会加上替换成本。本题只允许插入和删除,所以不匹配时,要么删除多余的字符,要么插入缺失的字符。 因此,对于不匹配的情况,转移方程为: dp[i][j] = min(dp[i-1][j] + deleteCost, dp[i][j-1] + insertCost) 。 综合起来: \[ dp[ i][ j ] = \begin{cases} dp[ i-1][ j-1] & \text{if } source[ i-1] == target[ j-1 ] \\ \min(dp[ i-1][ j] + deleteCost, dp[ i][ j-1 ] + insertCost) & \text{otherwise} \end{cases} \] 计算顺序 由于 dp[i][j] 依赖于 dp[i-1][j-1] 、 dp[i-1][j] 和 dp[i][j-1] ,我们可以按行或按列顺序计算,通常使用双重循环: 外层循环 i 从 0 到 n。 内层循环 j 从 0 到 m。 注意初始状态已经定义了 i=0 或 j=0 的情况,所以循环从 i=1 和 j=1 开始。 最终答案 最终答案为 dp[n][m] ,即将整个 source 转换为整个 target 的最小成本。 示例演算 以 source = "abc", target = "ac", insertCost = 5, deleteCost = 3 为例。 初始化: dp[0][0] = 0 dp[0][1] = 1 * 5 = 5 , dp[0][2] = 2 * 5 = 10 dp[1][0] = 1 * 3 = 3 , dp[2][0] = 2 * 3 = 6 , dp[3][0] = 3 * 3 = 9 计算 dp[1][1] :比较 source[ 0]='a' 和 target[ 0]='a',匹配,所以 dp[1][1] = dp[0][0] = 0 。 dp[1][2] :source[ 0]='a',target[ 1 ]='c',不匹配。 删除 'a': dp[0][2] + 3 = 10 + 3 = 13 插入 'c': dp[1][1] + 5 = 0 + 5 = 5 取 min = 5。 dp[2][1] :source[ 1]='b',target[ 0 ]='a',不匹配。 删除 'b': dp[1][1] + 3 = 0 + 3 = 3 插入 'a': dp[2][0] + 5 = 6 + 5 = 11 取 min = 3。 dp[2][2] :source[ 1]='b',target[ 1 ]='c',不匹配。 删除 'b': dp[1][2] + 3 = 5 + 3 = 8 插入 'c': dp[2][1] + 5 = 3 + 5 = 8 取 min = 8。 dp[3][1] :source[ 2]='c',target[ 0 ]='a',不匹配。 删除 'c': dp[2][1] + 3 = 3 + 3 = 6 插入 'a': dp[3][0] + 5 = 9 + 5 = 14 取 min = 6。 dp[3][2] :source[ 2]='c',target[ 1]='c',匹配!所以 dp[3][2] = dp[2][1] = 3 。 最终 dp[3][2] = 3 ,即最小成本为 3(删除 'b')。 时间复杂度与空间复杂度 时间复杂度:O(n * m),因为需要填充一个 (n+1) x (m+1) 的二维 DP 表。 空间复杂度:O(n * m) 用于存储 DP 表。可以优化到 O(min(n, m)) 使用滚动数组,但通常题目中 n 和 m 在 500 以内,直接二维数组即可。 总结 这个问题是编辑距离的简化版本,只允许插入和删除操作。通过定义 dp[i][j] 并合理考虑状态转移,我们可以高效地求出最小成本。关键点在于理解:当字符匹配时直接继承前状态;不匹配时,选择删除当前源字符或插入当前目标字符中成本较小的方案。