合并相邻字符的最小成本问题(相邻相同字符可合并为另一种字符,且合并有不同成本)
我将为你讲解一道区间动态规划的题目,它涉及字符串合并操作,并包含多种合并规则和成本。这道题是区间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) = 2cost(a, a, c) = 5cost(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(可以是任意小写字母)
状态转移方程为:
如果 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)) - 如果子串长度是1(即
-
初始化
- 将所有
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) = 1cost(a, a, b) = 2cost(b, b, a) = 3cost(b, b, b) = 1- 其他组合无效(成本为
INF)。
我们逐步计算:
长度为1的子串:
dp[0][0]['a'] = 0,dp[0][0]['b'] = INFdp[1][1]['a'] = 0,dp[1][1]['b'] = INFdp[2][2]['a'] = INF,dp[2][2]['b'] = 0dp[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的一个很好的练习。