合并相邻字符的最小成本问题(相邻相同字符可合并为另一种字符,且合并有不同成本)
字数 4869 2025-12-19 08:12:32

合并相邻字符的最小成本问题(相邻相同字符可合并为另一种字符,且合并有不同成本)

我将为你讲解一道区间动态规划的题目,它涉及字符串合并操作,并包含多种合并规则和成本。这道题是区间DP中较为复杂且有代表性的题目,能很好体现状态设计和决策枚举的思想。

题目描述

给定一个字符串 s,其字符集为小写字母。你有一系列合并操作:每次可以选择两个相邻的相同字符,将它们合并成一个新的字符(可能与原字符不同),合并需要消耗一定的成本。具体规则如下:

  1. 只有相邻相同的两个字符才能被合并。
  2. 合并后的新字符可以是任意小写字母(包括与原来相同或不同)。
  3. 对于每一对可合并的字符 (a, a) 和合并结果 b,有一个已知成本 cost(a, a, b)(其中 ab 是小写字母,a 可以是任意字符,b 是合并后的结果)。
  4. 合并操作是递归进行的:每次合并后,得到的新字符串长度减1,之后可以继续在合并后的新字符串上进行合并操作。
  5. 目标是:通过一系列合并操作,将原始字符串 s 最终合并为单个字符,并使得总合并成本最小

如果无法将字符串合并为单个字符,则返回 -1

示例:
假设字符集为 {a, b, c},合并成本规则如下(部分举例):

  • cost(a, a, a) = 1(两个 a 合并成 a 的成本是1)
  • cost(a, a, b) = 2
  • cost(a, a, c) = 5
  • cost(b, b, a) = 3
  • 其他组合可能无效(成本无穷大)。
    给定字符串 s = "aabb",问最小合并成本是多少?

解题思路

这道题的核心是:每次合并只能合并相邻且相同的字符,但合并后可以变成另一种字符。这会导致字符的变化,因此我们不仅要关心区间是否能合并,还要关心合并成什么字符。

我们可以用区间动态规划来解决:

  1. 定义状态
    dp[l][r][c] 表示将子串 s[l:r](左闭右闭区间)合并成单个字符 c 的最小成本。如果不可能合并成 c,则值为 INF(一个很大的数,表示不可行)。

  2. 状态转移
    考虑如何将区间 [l, r] 合并成字符 c

    • 如果子串长度是1(即 l == r),那么它本身就已经是单个字符。只有当 s[l] == c 时,成本为0(无需合并);否则不可行(INF)。
    • 如果长度大于1,我们需要找到一种合并顺序。因为每次合并的是两个相邻的相同字符,我们可以考虑最后一次合并
      最后一次合并一定是将区间 [l, k] 合并成的某个字符 x,与区间 [k+1, r] 合并成的某个字符 y 进行合并,并且要求 x == y(只有相同字符才能合并),然后合并成字符 c,此次合并的成本是 cost(x, x, c)
      但注意,这里的 xy 必须相同,我们设它们都是字符 m,则合并成 c 的成本是 cost(m, m, c)
      那么,总成本 = 将 [l, k] 合并成 m 的最小成本 + 将 [k+1, r] 合并成 m 的最小成本 + cost(m, m, c)

    因此,我们需要枚举:

    • 分割点 kl <= k < r
    • 中间字符 m(可以是任意小写字母)

    状态转移方程为:

    如果 l == r:
        dp[l][r][c] = 0  if s[l] == c else INF
    否则:
        dp[l][r][c] = min_{l <= k < r, m in 字符集} (dp[l][k][m] + dp[k+1][r][m] + cost(m, m, c))
    
  3. 初始化

    • 将所有 dp[l][r][c] 初始化为 INF
    • 对于长度为1的子串,设置 dp[i][i][s[i]] = 0
  4. 计算顺序
    按区间长度从小到大计算。因为大区间的计算依赖于小区间。

  5. 答案
    最终,整个字符串 s[0:n-1] 合并成单个字符的最小成本是 min_{c} dp[0][n-1][c]。如果所有 c 对应的值都是 INF,则返回 -1

详细示例

假设 s = "aabb",字符集为 {a, b},成本规则如下:

  • cost(a, a, a) = 1
  • cost(a, a, b) = 2
  • cost(b, b, a) = 3
  • cost(b, b, b) = 1
  • 其他组合无效(成本为 INF)。

我们逐步计算:

长度为1的子串

  • dp[0][0]['a'] = 0, dp[0][0]['b'] = INF
  • dp[1][1]['a'] = 0, dp[1][1]['b'] = INF
  • dp[2][2]['a'] = INF, dp[2][2]['b'] = 0
  • dp[3][3]['a'] = INF, dp[3][3]['b'] = 0

长度为2的子串

  • 区间 [0,1]:两个 "aa"
    枚举合并结果 c
    • 合并成 'a'cost(a, a, a) = 1,所以 dp[0][1]['a'] = 1
    • 合并成 'b'cost(a, a, b) = 2,所以 dp[0][1]['b'] = 2
  • 区间 [2,3]:两个 "bb"
    • 合并成 'a'cost(b, b, a) = 3,所以 dp[2][3]['a'] = 3
    • 合并成 'b'cost(b, b, b) = 1,所以 dp[2][3]['b'] = 1

长度为4的子串 [0,3](即 "aabb"):
我们要计算 dp[0][3][c],其中 c'a''b'

枚举分割点 k 和中间字符 m

  • 分割点 k=1(即分成 "aa""bb"):

    • 中间字符 m='a':需要将 [0,1] 合并成 'a'(成本1),将 [2,3] 合并成 'a'(成本3),然后合并 (a, a) 成目标字符 c
      如果目标 c='a',则合并成本 cost(a, a, a)=1,总成本 = 1+3+1=5。
      如果目标 c='b',则合并成本 cost(a, a, b)=2,总成本 = 1+3+2=6。
    • 中间字符 m='b':需要将 [0,1] 合并成 'b'(成本2),将 [2,3] 合并成 'b'(成本1),然后合并 (b, b) 成目标字符 c
      如果目标 c='a',则合并成本 cost(b, b, a)=3,总成本 = 2+1+3=6。
      如果目标 c='b',则合并成本 cost(b, b, b)=1,总成本 = 2+1+1=4。
  • 分割点 k=2(即分成 "aab""b"):
    注意:[0,2] 长度为3,我们还没计算它的 dp 值。实际上,我们需要先计算所有长度为3的子串。

长度为3的子串

  • 区间 [0,2]"aab"
    枚举分割点:

    • 分割点 k=0(分成 "a""ab"):"ab" 是长度为2的子串,但我们之前只计算了 "aa""bb",没有 "ab"。而且 "ab" 中的两个字符不同,无法直接合并,所以必须通过间接方式:先合并 "aa" 变成某个字符,再与 "b" 合并?等等,我们重新审视:对于长度为2的 "ab",由于两个字符不同,无法进行任何合并操作,所以它不可能合并成单个字符,因此 dp[1][2][c] 对所有 c 都是 INF。于是这个分割方案不可行。
    • 分割点 k=1(分成 "aa""b"):
      "aa" 合并成某个字符 m,然后与 "b" 合并。但注意:合并操作要求两个字符相同。所以必须让 m 等于 'b' 才能合并。那么:
      [0,1] 合并成 'b' 的最小成本是2(来自之前的计算结果)。
      [2,2] 合并成 'b' 的最小成本是0(因为 s[2] 就是 'b')。
      然后合并 (b, b) 成目标字符 c
      如果目标 c='a',则成本为 cost(b, b, a)=3,总成本 = 2+0+3=5。
      如果目标 c='b',则成本为 cost(b, b, b)=1,总成本 = 2+0+1=3。
      因此,dp[0][2]['a'] = 5, dp[0][2]['b'] = 3
  • 区间 [1,3]"abb"
    类似地,枚举分割点:

    • 分割点 k=1(分成 "a""bb"):"bb" 可以合并成某个字符 m,且需要 m 等于 'a' 才能与左边的 'a' 合并。但 "bb" 合并成 'a' 的成本是3,合并成 'b' 的成本是1。要合并 (a, a)(a, b)?只有相同字符才能合并,所以必须是 m='a'。那么:
      [1,1] 合并成 'a' 成本0,将 [2,3] 合并成 'a' 成本3,合并 (a, a) 成目标字符 c
      如果目标 c='a'cost(a, a, a)=1,总成本 = 0+3+1=4。
      如果目标 c='b'cost(a, a, b)=2,总成本 = 0+3+2=5。
    • 分割点 k=2(分成 "ab""b"):"ab" 长度为2且字符不同,不可合并成单个字符,所以不可行。
      因此,dp[1][3]['a'] = 4, dp[1][3]['b'] = 5

现在回到长度为4的区间 [0,3]
分割点 k=1 的情况我们已算过(上面得出候选成本5,6,6,4)。
分割点 k=2 的情况:

  • 中间字符 m='a':需要将 [0,2] 合并成 'a'(成本5),将 [3,3] 合并成 'a'(不可行,因为 s[3]='b' 且无法变成 'a' 作为单个字符),所以不可行。
  • 中间字符 m='b':需要将 [0,2] 合并成 'b'(成本3),将 [3,3] 合并成 'b'(成本0),然后合并 (b, b) 成目标字符 c
    如果目标 c='a',合并成本 cost(b, b, a)=3,总成本 = 3+0+3=6。
    如果目标 c='b',合并成本 cost(b, b, b)=1,总成本 = 3+0+1=4。

综上,我们比较所有候选:

  • 目标 'a' 的候选成本:分割点 k=1 中间字符 'a' 得到5,分割点 k=2 中间字符 'b' 得到6,所以最小是5。
  • 目标 'b' 的候选成本:分割点 k=1 中间字符 'b' 得到4,分割点 k=2 中间字符 'b' 得到4,所以最小是4。

因此,dp[0][3]['a'] = 5, dp[0][3]['b'] = 4

最终答案:最小成本是 min(5, 4) = 4,合并成字符 'b'

算法复杂度

  • 状态数量:O(n^2 * C),其中 n 是字符串长度,C 是字符集大小(本题为26)。
  • 状态转移:需要枚举分割点 k 和中间字符 m,复杂度 O(n * C)
  • 总时间复杂度:O(n^3 * C^2)。在本题中 C=26 是常数,所以可视为 O(n^3)
  • 空间复杂度:O(n^2 * C)

优化与扩展

  • 实际实现时,cost 可以用三维数组或哈希表存储。
  • 注意处理不可行的情况(INF)。
  • 本题的合并规则是“相邻相同字符合并”,但合并后字符可以变化,这使得问题比简单的相邻合并更复杂,需要同时跟踪区间合并成的字符。

这道题综合了区间划分、状态设计和多阶段决策,是区间DP的一个很好的练习。

合并相邻字符的最小成本问题(相邻相同字符可合并为另一种字符,且合并有不同成本) 我将为你讲解一道区间动态规划的题目,它涉及字符串合并操作,并包含多种合并规则和成本。这道题是区间DP中较为复杂且有代表性的题目,能很好体现状态设计和决策枚举的思想。 题目描述 给定一个字符串 s ,其字符集为小写字母。你有一系列合并操作:每次可以选择 两个相邻的相同字符 ,将它们合并成一个 新的字符 (可能与原字符不同),合并需要消耗一定的成本。具体规则如下: 只有 相邻 且 相同 的两个字符才能被合并。 合并后的新字符可以是任意小写字母(包括与原来相同或不同)。 对于每一对可合并的字符 (a, a) 和合并结果 b ,有一个已知成本 cost(a, a, b) (其中 a 和 b 是小写字母, a 可以是任意字符, b 是合并后的结果)。 合并操作是递归进行的:每次合并后,得到的新字符串长度减1,之后可以继续在合并后的新字符串上进行合并操作。 目标是:通过一系列合并操作,将原始字符串 s 最终合并为 单个字符 ,并使得 总合并成本最小 。 如果无法将字符串合并为单个字符,则返回 -1 。 示例: 假设字符集为 {a, b, c} ,合并成本规则如下(部分举例): cost(a, a, a) = 1 (两个 a 合并成 a 的成本是1) cost(a, a, b) = 2 cost(a, a, c) = 5 cost(b, b, a) = 3 其他组合可能无效(成本无穷大)。 给定字符串 s = "aabb" ,问最小合并成本是多少? 解题思路 这道题的核心是:每次合并只能合并相邻且相同的字符,但合并后可以变成另一种字符。这会导致字符的变化,因此我们不仅要关心区间是否能合并,还要关心合并成什么字符。 我们可以用区间动态规划来解决: 定义状态 设 dp[l][r][c] 表示将子串 s[l:r] (左闭右闭区间)合并成 单个字符 c 的最小成本。如果不可能合并成 c ,则值为 INF (一个很大的数,表示不可行)。 状态转移 考虑如何将区间 [l, r] 合并成字符 c : 如果子串长度是1(即 l == r ),那么它本身就已经是单个字符。只有当 s[l] == c 时,成本为0(无需合并);否则不可行( INF )。 如果长度大于1,我们需要找到一种合并顺序。因为每次合并的是两个相邻的相同字符,我们可以考虑 最后一次合并 : 最后一次合并一定是将区间 [l, k] 合并成的某个字符 x ,与区间 [k+1, r] 合并成的某个字符 y 进行合并,并且要求 x == y (只有相同字符才能合并),然后合并成字符 c ,此次合并的成本是 cost(x, x, c) 。 但注意,这里的 x 和 y 必须相同,我们设它们都是字符 m ,则合并成 c 的成本是 cost(m, m, c) 。 那么,总成本 = 将 [l, k] 合并成 m 的最小成本 + 将 [k+1, r] 合并成 m 的最小成本 + cost(m, m, c) 。 因此,我们需要枚举: 分割点 k ( l <= k < r ) 中间字符 m (可以是任意小写字母) 状态转移方程为: 初始化 将所有 dp[l][r][c] 初始化为 INF 。 对于长度为1的子串,设置 dp[i][i][s[i]] = 0 。 计算顺序 按区间长度从小到大计算。因为大区间的计算依赖于小区间。 答案 最终,整个字符串 s[0:n-1] 合并成单个字符的最小成本是 min_{c} dp[0][n-1][c] 。如果所有 c 对应的值都是 INF ,则返回 -1 。 详细示例 假设 s = "aabb" ,字符集为 {a, b} ,成本规则如下: cost(a, a, a) = 1 cost(a, a, b) = 2 cost(b, b, a) = 3 cost(b, b, b) = 1 其他组合无效(成本为 INF )。 我们逐步计算: 长度为1的子串 : dp[0][0]['a'] = 0 , dp[0][0]['b'] = INF dp[1][1]['a'] = 0 , dp[1][1]['b'] = INF dp[2][2]['a'] = INF , dp[2][2]['b'] = 0 dp[3][3]['a'] = INF , dp[3][3]['b'] = 0 长度为2的子串 : 区间 [0,1] :两个 "aa" 枚举合并结果 c : 合并成 'a' : cost(a, a, a) = 1 ,所以 dp[0][1]['a'] = 1 合并成 'b' : cost(a, a, b) = 2 ,所以 dp[0][1]['b'] = 2 区间 [2,3] :两个 "bb" 合并成 'a' : cost(b, b, a) = 3 ,所以 dp[2][3]['a'] = 3 合并成 'b' : cost(b, b, b) = 1 ,所以 dp[2][3]['b'] = 1 长度为4的子串 [0,3] (即 "aabb" ): 我们要计算 dp[0][3][c] ,其中 c 是 'a' 或 'b' 。 枚举分割点 k 和中间字符 m : 分割点 k=1 (即分成 "aa" 和 "bb" ): 中间字符 m='a' :需要将 [0,1] 合并成 'a' (成本1),将 [2,3] 合并成 'a' (成本3),然后合并 (a, a) 成目标字符 c : 如果目标 c='a' ,则合并成本 cost(a, a, a)=1 ,总成本 = 1+3+1=5。 如果目标 c='b' ,则合并成本 cost(a, a, b)=2 ,总成本 = 1+3+2=6。 中间字符 m='b' :需要将 [0,1] 合并成 'b' (成本2),将 [2,3] 合并成 'b' (成本1),然后合并 (b, b) 成目标字符 c : 如果目标 c='a' ,则合并成本 cost(b, b, a)=3 ,总成本 = 2+1+3=6。 如果目标 c='b' ,则合并成本 cost(b, b, b)=1 ,总成本 = 2+1+1=4。 分割点 k=2 (即分成 "aab" 和 "b" ): 注意: [0,2] 长度为3,我们还没计算它的 dp 值。实际上,我们需要先计算所有长度为3的子串。 长度为3的子串 : 区间 [0,2] : "aab" 枚举分割点: 分割点 k=0 (分成 "a" 和 "ab" ): "ab" 是长度为2的子串,但我们之前只计算了 "aa" 和 "bb" ,没有 "ab" 。而且 "ab" 中的两个字符不同,无法直接合并,所以必须通过间接方式:先合并 "aa" 变成某个字符,再与 "b" 合并?等等,我们重新审视:对于长度为2的 "ab" ,由于两个字符不同,无法进行任何合并操作,所以它 不可能 合并成单个字符,因此 dp[1][2][c] 对所有 c 都是 INF 。于是这个分割方案不可行。 分割点 k=1 (分成 "aa" 和 "b" ): 将 "aa" 合并成某个字符 m ,然后与 "b" 合并。但注意:合并操作要求两个字符相同。所以必须让 m 等于 'b' 才能合并。那么: 将 [0,1] 合并成 'b' 的最小成本是2(来自之前的计算结果)。 将 [2,2] 合并成 'b' 的最小成本是0(因为 s[2] 就是 'b' )。 然后合并 (b, b) 成目标字符 c : 如果目标 c='a' ,则成本为 cost(b, b, a)=3 ,总成本 = 2+0+3=5。 如果目标 c='b' ,则成本为 cost(b, b, b)=1 ,总成本 = 2+0+1=3。 因此, dp[0][2]['a'] = 5 , dp[0][2]['b'] = 3 。 区间 [1,3] : "abb" 类似地,枚举分割点: 分割点 k=1 (分成 "a" 和 "bb" ): "bb" 可以合并成某个字符 m ,且需要 m 等于 'a' 才能与左边的 'a' 合并。但 "bb" 合并成 'a' 的成本是3,合并成 'b' 的成本是1。要合并 (a, a) 或 (a, b) ?只有相同字符才能合并,所以必须是 m='a' 。那么: 将 [1,1] 合并成 'a' 成本0,将 [2,3] 合并成 'a' 成本3,合并 (a, a) 成目标字符 c : 如果目标 c='a' , cost(a, a, a)=1 ,总成本 = 0+3+1=4。 如果目标 c='b' , cost(a, a, b)=2 ,总成本 = 0+3+2=5。 分割点 k=2 (分成 "ab" 和 "b" ): "ab" 长度为2且字符不同,不可合并成单个字符,所以不可行。 因此, dp[1][3]['a'] = 4 , dp[1][3]['b'] = 5 。 现在回到长度为4的区间 [0,3] : 分割点 k=1 的情况我们已算过(上面得出候选成本5,6,6,4)。 分割点 k=2 的情况: 中间字符 m='a' :需要将 [0,2] 合并成 'a' (成本5),将 [3,3] 合并成 'a' (不可行,因为 s[3]='b' 且无法变成 'a' 作为单个字符),所以不可行。 中间字符 m='b' :需要将 [0,2] 合并成 'b' (成本3),将 [3,3] 合并成 'b' (成本0),然后合并 (b, b) 成目标字符 c : 如果目标 c='a' ,合并成本 cost(b, b, a)=3 ,总成本 = 3+0+3=6。 如果目标 c='b' ,合并成本 cost(b, b, b)=1 ,总成本 = 3+0+1=4。 综上,我们比较所有候选: 目标 'a' 的候选成本:分割点 k=1 中间字符 'a' 得到5,分割点 k=2 中间字符 'b' 得到6,所以最小是5。 目标 'b' 的候选成本:分割点 k=1 中间字符 'b' 得到4,分割点 k=2 中间字符 'b' 得到4,所以最小是4。 因此, dp[0][3]['a'] = 5 , dp[0][3]['b'] = 4 。 最终答案:最小成本是 min(5, 4) = 4 ,合并成字符 'b' 。 算法复杂度 状态数量: O(n^2 * C) ,其中 n 是字符串长度, C 是字符集大小(本题为26)。 状态转移:需要枚举分割点 k 和中间字符 m ,复杂度 O(n * C) 。 总时间复杂度: O(n^3 * C^2) 。在本题中 C=26 是常数,所以可视为 O(n^3) 。 空间复杂度: O(n^2 * C) 。 优化与扩展 实际实现时, cost 可以用三维数组或哈希表存储。 注意处理不可行的情况( INF )。 本题的合并规则是“相邻相同字符合并”,但合并后字符可以变化,这使得问题比简单的相邻合并更复杂,需要同时跟踪区间合并成的字符。 这道题综合了区间划分、状态设计和多阶段决策,是区间DP的一个很好的练习。