奇怪的打印机 II(最小打印次数问题,颜色数量有限的最少打印次数)
字数 5914 2025-12-12 19:45:12

奇怪的打印机 II(最小打印次数问题,颜色数量有限的最少打印次数)

题目描述
我们有一台奇怪的打印机。它有一个长度为 n 的目标字符串 target,每个字符 target[i] 是 26 个小写字母之一,表示一种颜色。打印机初始时有一张白纸(可以视为由空格组成的字符串,或者任意空白字符)。每次操作,打印机可以选择一个连续区间 [i, j],并用一种颜色(一个小写字母)覆盖这个区间内的所有字符。注意,每次打印必须使用单一颜色,且会完全覆盖区间内原有的字符(无论原来是什么颜色)。打印机可以执行任意多次操作。
问:打印出恰好等于目标字符串 target 所需的最少操作次数是多少?

此外,题目有一个进阶条件:目标字符串中最多只包含 k 种不同颜色(字母)。本题中 k 通常较小(例如 k ≤ 5 或 k ≤ 6)。

示例
target = "ababa"
输出:3
解释:最少三次操作:第一次打印整个区间为 'a' 得到 "aaaaa";第二次打印区间 [1,3] 为 'b' 得到 "abbba";第三次打印区间 [2,2] 为 'a' 得到 "ababa"。

限制

  • 1 ≤ n ≤ 100
  • target 仅包含小写字母
  • 颜色种类 k ≤ 6(或类似小常数)

解题思路
这是一道区间动态规划(interval DP)题目,与之前讲过的“奇怪的打印机”问题相似,但多了一个条件:颜色数量有限。这允许我们用状态压缩来优化,将颜色集合作为状态的一部分。
核心思路:

  1. 如果区间两端字符相同,可以在第一次打印整个区间时直接使用这个颜色,从而减少一次操作。
  2. 但颜色有限,我们可以在状态中记录“已使用的颜色集合”,以便复用已有颜色。
  3. 最终目标是打印整个字符串,并且使用的颜色集合是 target 中实际出现的颜色集合的子集。

第一步:状态定义
设 dp[l][r][mask] 表示将区间 [l, r] 打印成 target[l…r] 所需的最少操作次数,且打印过程中使用的颜色集合恰好是 mask(mask 是一个二进制数,第 c 位为 1 表示颜色 c 被使用过)。
颜色编号:我们可以将 target 中出现的不同颜色映射到 0 到 k-1。

第二步:状态转移
考虑区间 [l, r]:

  1. 如果 l == r,则只需一次操作(打印这个字符),mask 中只有这个字符对应的位为 1。
  2. 如果 l < r,我们考虑第一次操作打印的颜色和区间。
    • 情况 A:第一次操作打印整个区间 [l, r] 为颜色 c(必须与 target[l] 相同吗?不一定,但如果第一次打印的颜色与最终 target[l] 不同,则后续还要覆盖,显然不优,所以第一次打印的颜色应该与 target[l] 相同)。事实上,我们可以证明存在最优解,使得第一次操作打印的颜色与 target[l] 相同,且打印的区间左端点为 l。
    • 情况 B:第一次操作只打印区间 [l, m](m ≥ l),颜色为 target[l]。之后分别处理子区间。

实际上,更高效的方法是:
第一次操作打印颜色 c = target[l],覆盖的区间是 [l, p],其中 p 是满足 target[p] == c 且 p 尽可能大的位置(不一定要连续,但为了最少操作,应尽量覆盖所有相同颜色的位置)。
然后区间被分成若干段,每一段是连续的不等于 c 的区间,对每一段递归求解。

但这样需要枚举 p,时间复杂度较高。

更通用的区间 DP 思路(适用于颜色数 k 小的情况):
我们定义 dp[l][r][mask] 为最少操作次数。
转移时,我们考虑第一次操作打印的颜色 c 在 mask 中(必须使用),然后枚举这个颜色 c 在区间 [l, r] 中覆盖的若干段。但这样很复杂。

另一种更简单的状态设计(不显式记录 mask):
设 f[l][r] 表示打印区间 [l, r] 成 target[l…r] 的最少操作次数。
转移方程:

  1. 如果 target[l] == target[r],那么第一次操作可以打印颜色 target[l] 覆盖整个区间,之后只需要处理中间部分。但注意:如果中间有不同颜色,可能需要额外操作。
    我们可以考虑:f[l][r] = f[l][r-1] 或者 f[l+1][r](因为最后打印 target[l] 时可以把右端点一起覆盖)。
    实际上,当 target[l] == target[r] 时,最优策略是第一次打印整个区间为 target[l],然后递归处理去掉两端相同颜色后的区间。但这样不一定最优,因为中间可能有同色区间可以合并打印。

更正确的方程:
f[l][r] = min( f[l][k] + f[k+1][r] ) for k in [l, r-1]
但这样没有利用“同色可一起打印”的性质,会超时。

优化:考虑如果 target[l] == target[r],我们可以让第一次打印颜色 target[l] 覆盖 l 和 r 以及中间所有同色的位置,然后中间的不同色的区间分别处理。
即:找到所有与 target[l] 相同颜色的位置,将它们视为一个整体,第一次打印这些位置,然后处理中间的段。

实现时,我们可以用如下递推:
f[l][r] = min( f[l+1][r], f[l][r-1] ) if target[l] == target[r]
因为如果两端相同,可以先打印整个区间为 target[l],然后处理内部,但这样会多一次操作,所以不是 min(f[l+1][r], f[l][r-1]),而是 f[l][r] = f[l][r-1] 或者 f[l+1][r]?
实际上,考虑打印区间 [l, r],如果 target[l] == target[r],我们可以先打印 [l, r] 为 target[l],然后递归处理 [l, r-1] 或 [l+1, r](因为右端点已被覆盖,只需要处理左边部分)。
更准确:f[l][r] = f[l][r-1] (即先打印 [l, r] 为 target[l],则 r 已正确,只需处理 [l, r-1])。

但这是假设我们在最后一次操作才打印 target[l],不一定最优。

标准解法(LeetCode 546 移除盒子的思路类似,但颜色数有限):
我们定义 dp[l][r][k] 表示在区间 [l, r] 后面还有 k 个与 target[l] 相同颜色的字符已经打印好的情况下,完成该区间的最少操作次数。
但本题是打印而非移除,所以需调整。

更简单的解法(针对颜色数 k ≤ 6):
状态:dp[l][r][mask] 表示将区间 [l, r] 打印成目标,且使用的颜色集合是 mask 的最少操作次数。
转移:

  1. 如果 mask 中只有一种颜色,且区间内所有字符都是这个颜色,则 dp=1。
  2. 否则,枚举第一次操作打印的颜色 c(在 mask 中),然后枚举这个颜色 c 在区间中覆盖的所有位置(这些位置必须是目标中就是颜色 c 的)。
    但这样复杂度太高。

实际高效解法(区间 DP + 状态压缩优化,k 小):
我们预处理每个位置的颜色编号 0~k-1。
状态:dp[l][r][mask] 表示将区间 [l, r] 打印成目标,且这个区间最终使用的颜色集合是 mask 的最少操作次数。
转移:

  • 如果 mask 中只有一种颜色 c,则 dp[l][r][mask] = 1 如果区间 [l, r] 中所有目标颜色都是 c,否则为无穷大。
  • 否则,枚举分割点 m,将区间分成 [l, m] 和 [m+1, r],它们使用的颜色集合分别是 mask1 和 mask2,且 mask1 ∪ mask2 = mask,mask1 ∩ mask2 可以非空(因为颜色可复用)。
    则 dp[l][r][mask] = min( dp[l][m][mask1] + dp[m+1][r][mask2] - count(mask1 ∩ mask2) ),其中 count 是 mask1 和 mask2 交集的大小(重复颜色被多算了一次操作,需减去)。

但这样要枚举子集,复杂度 O(n^3 * 3^k),对于 n=100, k=6 勉强可行。

最终简化版本(常用解法):
我们只定义 dp[l][r] 为打印区间 [l, r] 的最少操作次数,不考虑颜色集合。
转移:

  1. dp[l][r] = dp[l][r-1] + 1 (先打印 [l, r-1],再打印 r)
  2. 如果 target[k] == target[r] 对于某个 k 在 [l, r-1] 中,则可以将 k 和 r 在一次操作中打印(颜色为 target[r]),然后中间部分另外处理。
    即:dp[l][r] = min( dp[l][k] + dp[k+1][r-1] ) for k in [l, r-1] where target[k] == target[r]

这是因为,当 target[k] == target[r] 时,我们可以先打印区间 [k, r] 为 target[r],然后分别处理 [l, k-1] 和 [k+1, r-1]。但这样会多一次操作,所以方程是 dp[l][r] = min( dp[l][k-1] + dp[k+1][r-1] )?

实际上,标准方程是:
dp[l][r] = min( dp[l][k-1] + dp[k][r-1] ) if target[k] == target[r]?

更准确的方程(来自 LeetCode 664 奇怪的打印机,但这里颜色有限不影响这个方程):
dp[l][r] = dp[l][r-1] + 1
dp[l][r] = min( dp[l][k] + dp[k+1][r-1] ) for k in [l, r-1] where target[k] == target[r]

实现步骤(最终采用 k 小的优化版):

  1. 将 target 中不同颜色映射到 0~k-1。

  2. 预处理每个区间是否全为同色。

  3. 动态规划:
    dp[l][r] 初始化 = dp[l][r-1] + 1。
    枚举 k 从 l 到 r-1,如果 target[k] == target[r],则
    dp[l][r] = min(dp[l][r], dp[l][k-1] + dp[k][r-1]) (注意边界)
    或者 dp[l][r] = min(dp[l][r], dp[l][k] + dp[k+1][r-1])。

    实际上,当 target[k] == target[r] 时,我们可以将 k 和 r 在同一次操作打印,所以区间 [k, r] 可以视为一次操作,然后分别处理 [l, k-1] 和 [k+1, r-1],但 k+1 可能大于 r-1,此时第二部分为 0。
    所以方程:
    dp[l][r] = min(dp[l][r], dp[l][k-1] + dp[k+1][r-1]) if target[k] == target[r]

  4. 最终答案 = dp[0][n-1]。

复杂度:O(n^3),对于 n=100 可行。颜色种类限制 k 在本解法中未直接利用,但实际运行更快因为相同颜色判断更少。


举例说明
target = "ababa"
n=5, 颜色 a,b。
初始化 dp[i][i]=1。
计算 dp[0][1]: 'a','b' 不同,所以 dp[0][1] = dp[0][0]+1=2。
dp[1][2]: 'b','a' -> 2。
dp[0][2]: 'a','b','a'
首先 dp[0][2] = dp[0][1]+1=3。
然后 k=0: target[0]=='a' 但 target[2]=='a' 相等,所以 dp[0][2] = min(3, dp[0][-1] + dp[1][1]),注意边界。
实际上,当 k=0,区间分 [l, k-1] 和 [k+1, r-1] 即 [0,-1] 和 [1,1]。
dp[0][-1] 表示空区间,操作数 0。dp[1][1]=1,所以总 1,但还需要一次操作打印 target[0] 和 target[2] 为 'a',所以是 1+1=2?
我们定义 dp[l][r] 为打印区间 [l,r] 的最少次数,当 k=0 时,我们可以在一次操作打印 [0,2] 为 'a',然后处理 [1,1] 为 'b',所以总次数 = 1 + dp[1][1] = 2。
所以方程应该是:
dp[l][r] = min(dp[l][r], dp[l][k-1] + 1 + dp[k+1][r-1])?
但 k=0 时,dp[l][k-1] 是空区间 0,dp[k+1][r-1] 是 [1,1] 即 1,再加 1 得 2。正确。

所以最终方程为:
dp[l][r] = min(dp[l][r], dp[l][k-1] + 1 + dp[k+1][r-1]) if target[k] == target[r]
且当 k+1 > r-1 时,dp[k+1][r-1] = 0。

按此计算,最终 dp[0][4] = 3。


代码框架(Python)

def strangePrinterII(target):
    n = len(target)
    dp = [[0]*n for _ in range(n)]
    for i in range(n):
        dp[i][i] = 1
    for length in range(2, n+1):
        for l in range(0, n-length+1):
            r = l+length-1
            dp[l][r] = dp[l][r-1] + 1
            for k in range(l, r):
                if target[k] == target[r]:
                    left = dp[l][k-1] if k > l else 0
                    right = dp[k+1][r-1] if k+1 <= r-1 else 0
                    dp[l][r] = min(dp[l][r], left + 1 + right)
    return dp[0][n-1]

总结
这道题是区间 DP 的经典应用,通过判断区间两端或中间相同颜色来减少操作次数。颜色种类有限的条件在标准解法中不影响状态转移,但若 k 极小,可用状态压缩进一步优化。核心是找到最优的第一次打印区间,使得相同颜色尽量一起打印,从而减少操作次数。

奇怪的打印机 II(最小打印次数问题,颜色数量有限的最少打印次数) 题目描述 我们有一台奇怪的打印机。它有一个长度为 n 的目标字符串 target,每个字符 target[ i] 是 26 个小写字母之一,表示一种颜色。打印机初始时有一张白纸(可以视为由空格组成的字符串,或者任意空白字符)。每次操作,打印机可以选择一个连续区间 [ i, j ],并用一种颜色(一个小写字母)覆盖这个区间内的所有字符。注意,每次打印必须使用单一颜色,且会完全覆盖区间内原有的字符(无论原来是什么颜色)。打印机可以执行任意多次操作。 问:打印出恰好等于目标字符串 target 所需的最少操作次数是多少? 此外,题目有一个进阶条件:目标字符串中最多只包含 k 种不同颜色(字母)。本题中 k 通常较小(例如 k ≤ 5 或 k ≤ 6)。 示例 target = "ababa" 输出:3 解释:最少三次操作:第一次打印整个区间为 'a' 得到 "aaaaa";第二次打印区间 [ 1,3] 为 'b' 得到 "abbba";第三次打印区间 [ 2,2 ] 为 'a' 得到 "ababa"。 限制 1 ≤ n ≤ 100 target 仅包含小写字母 颜色种类 k ≤ 6(或类似小常数) 解题思路 这是一道区间动态规划(interval DP)题目,与之前讲过的“奇怪的打印机”问题相似,但多了一个条件:颜色数量有限。这允许我们用状态压缩来优化,将颜色集合作为状态的一部分。 核心思路: 如果区间两端字符相同,可以在第一次打印整个区间时直接使用这个颜色,从而减少一次操作。 但颜色有限,我们可以在状态中记录“已使用的颜色集合”,以便复用已有颜色。 最终目标是打印整个字符串,并且使用的颜色集合是 target 中实际出现的颜色集合的子集。 第一步:状态定义 设 dp[ l][ r][ mask] 表示将区间 [ l, r] 打印成 target[ l…r ] 所需的最少操作次数,且打印过程中使用的颜色集合恰好是 mask(mask 是一个二进制数,第 c 位为 1 表示颜色 c 被使用过)。 颜色编号:我们可以将 target 中出现的不同颜色映射到 0 到 k-1。 第二步:状态转移 考虑区间 [ l, r ]: 如果 l == r,则只需一次操作(打印这个字符),mask 中只有这个字符对应的位为 1。 如果 l < r,我们考虑第一次操作打印的颜色和区间。 情况 A:第一次操作打印整个区间 [ l, r] 为颜色 c(必须与 target[ l] 相同吗?不一定,但如果第一次打印的颜色与最终 target[ l] 不同,则后续还要覆盖,显然不优,所以第一次打印的颜色应该与 target[ l] 相同)。事实上,我们可以证明存在最优解,使得第一次操作打印的颜色与 target[ l ] 相同,且打印的区间左端点为 l。 情况 B:第一次操作只打印区间 [ l, m](m ≥ l),颜色为 target[ l ]。之后分别处理子区间。 实际上,更高效的方法是: 第一次操作打印颜色 c = target[ l],覆盖的区间是 [ l, p],其中 p 是满足 target[ p ] == c 且 p 尽可能大的位置(不一定要连续,但为了最少操作,应尽量覆盖所有相同颜色的位置)。 然后区间被分成若干段,每一段是连续的不等于 c 的区间,对每一段递归求解。 但这样需要枚举 p,时间复杂度较高。 更通用的区间 DP 思路 (适用于颜色数 k 小的情况): 我们定义 dp[ l][ r][ mask ] 为最少操作次数。 转移时,我们考虑第一次操作打印的颜色 c 在 mask 中(必须使用),然后枚举这个颜色 c 在区间 [ l, r ] 中覆盖的若干段。但这样很复杂。 另一种更简单的状态设计 (不显式记录 mask): 设 f[ l][ r] 表示打印区间 [ l, r] 成 target[ l…r ] 的最少操作次数。 转移方程: 如果 target[ l] == target[ r],那么第一次操作可以打印颜色 target[ l ] 覆盖整个区间,之后只需要处理中间部分。但注意:如果中间有不同颜色,可能需要额外操作。 我们可以考虑:f[ l][ r] = f[ l][ r-1] 或者 f[ l+1][ r](因为最后打印 target[ l ] 时可以把右端点一起覆盖)。 实际上,当 target[ l] == target[ r] 时,最优策略是第一次打印整个区间为 target[ l ],然后递归处理去掉两端相同颜色后的区间。但这样不一定最优,因为中间可能有同色区间可以合并打印。 更正确的方程: f[ l][ r] = min( f[ l][ k] + f[ k+1][ r] ) for k in [ l, r-1 ] 但这样没有利用“同色可一起打印”的性质,会超时。 优化 :考虑如果 target[ l] == target[ r],我们可以让第一次打印颜色 target[ l ] 覆盖 l 和 r 以及中间所有同色的位置,然后中间的不同色的区间分别处理。 即:找到所有与 target[ l ] 相同颜色的位置,将它们视为一个整体,第一次打印这些位置,然后处理中间的段。 实现时,我们可以用如下递推: f[ l][ r] = min( f[ l+1][ r], f[ l][ r-1] ) if target[ l] == target[ r ] 因为如果两端相同,可以先打印整个区间为 target[ l],然后处理内部,但这样会多一次操作,所以不是 min(f[ l+1][ r], f[ l][ r-1]),而是 f[ l][ r] = f[ l][ r-1] 或者 f[ l+1][ r ]? 实际上,考虑打印区间 [ l, r],如果 target[ l] == target[ r],我们可以先打印 [ l, r] 为 target[ l],然后递归处理 [ l, r-1] 或 [ l+1, r ](因为右端点已被覆盖,只需要处理左边部分)。 更准确:f[ l][ r] = f[ l][ r-1] (即先打印 [ l, r] 为 target[ l],则 r 已正确,只需处理 [ l, r-1 ])。 但这是假设我们在最后一次操作才打印 target[ l ],不一定最优。 标准解法 (LeetCode 546 移除盒子的思路类似,但颜色数有限): 我们定义 dp[ l][ r][ k] 表示在区间 [ l, r] 后面还有 k 个与 target[ l ] 相同颜色的字符已经打印好的情况下,完成该区间的最少操作次数。 但本题是打印而非移除,所以需调整。 更简单的解法 (针对颜色数 k ≤ 6): 状态:dp[ l][ r][ mask] 表示将区间 [ l, r ] 打印成目标,且使用的颜色集合是 mask 的最少操作次数。 转移: 如果 mask 中只有一种颜色,且区间内所有字符都是这个颜色,则 dp=1。 否则,枚举第一次操作打印的颜色 c(在 mask 中),然后枚举这个颜色 c 在区间中覆盖的所有位置(这些位置必须是目标中就是颜色 c 的)。 但这样复杂度太高。 实际高效解法 (区间 DP + 状态压缩优化,k 小): 我们预处理每个位置的颜色编号 0~k-1。 状态:dp[ l][ r][ mask] 表示将区间 [ l, r ] 打印成目标,且这个区间最终使用的颜色集合是 mask 的最少操作次数。 转移: 如果 mask 中只有一种颜色 c,则 dp[ l][ r][ mask] = 1 如果区间 [ l, r ] 中所有目标颜色都是 c,否则为无穷大。 否则,枚举分割点 m,将区间分成 [ l, m] 和 [ m+1, r ],它们使用的颜色集合分别是 mask1 和 mask2,且 mask1 ∪ mask2 = mask,mask1 ∩ mask2 可以非空(因为颜色可复用)。 则 dp[ l][ r][ mask] = min( dp[ l][ m][ mask1] + dp[ m+1][ r][ mask2 ] - count(mask1 ∩ mask2) ),其中 count 是 mask1 和 mask2 交集的大小(重复颜色被多算了一次操作,需减去)。 但这样要枚举子集,复杂度 O(n^3 * 3^k),对于 n=100, k=6 勉强可行。 最终简化版本 (常用解法): 我们只定义 dp[ l][ r] 为打印区间 [ l, r ] 的最少操作次数,不考虑颜色集合。 转移: dp[ l][ r] = dp[ l][ r-1] + 1 (先打印 [ l, r-1 ],再打印 r) 如果 target[ k] == target[ r] 对于某个 k 在 [ l, r-1] 中,则可以将 k 和 r 在一次操作中打印(颜色为 target[ r ]),然后中间部分另外处理。 即:dp[ l][ r] = min( dp[ l][ k] + dp[ k+1][ r-1] ) for k in [ l, r-1] where target[ k] == target[ r ] 这是因为,当 target[ k] == target[ r] 时,我们可以先打印区间 [ k, r] 为 target[ r],然后分别处理 [ l, k-1] 和 [ k+1, r-1]。但这样会多一次操作,所以方程是 dp[ l][ r] = min( dp[ l][ k-1] + dp[ k+1][ r-1 ] )? 实际上,标准方程是: dp[ l][ r] = min( dp[ l][ k-1] + dp[ k][ r-1] ) if target[ k] == target[ r ]? 更准确的方程(来自 LeetCode 664 奇怪的打印机,但这里颜色有限不影响这个方程): dp[ l][ r] = dp[ l][ r-1 ] + 1 dp[ l][ r] = min( dp[ l][ k] + dp[ k+1][ r-1] ) for k in [ l, r-1] where target[ k] == target[ r ] 实现步骤 (最终采用 k 小的优化版): 将 target 中不同颜色映射到 0~k-1。 预处理每个区间是否全为同色。 动态规划: dp[ l][ r] 初始化 = dp[ l][ r-1 ] + 1。 枚举 k 从 l 到 r-1,如果 target[ k] == target[ r ],则 dp[ l][ r] = min(dp[ l][ r], dp[ l][ k-1] + dp[ k][ r-1 ]) (注意边界) 或者 dp[ l][ r] = min(dp[ l][ r], dp[ l][ k] + dp[ k+1][ r-1 ])。 实际上,当 target[ k] == target[ r] 时,我们可以将 k 和 r 在同一次操作打印,所以区间 [ k, r] 可以视为一次操作,然后分别处理 [ l, k-1] 和 [ k+1, r-1 ],但 k+1 可能大于 r-1,此时第二部分为 0。 所以方程: dp[ l][ r] = min(dp[ l][ r], dp[ l][ k-1] + dp[ k+1][ r-1]) if target[ k] == target[ r ] 最终答案 = dp[ 0][ n-1 ]。 复杂度 :O(n^3),对于 n=100 可行。颜色种类限制 k 在本解法中未直接利用,但实际运行更快因为相同颜色判断更少。 举例说明 target = "ababa" n=5, 颜色 a,b。 初始化 dp[ i][ i ]=1。 计算 dp[ 0][ 1]: 'a','b' 不同,所以 dp[ 0][ 1] = dp[ 0][ 0 ]+1=2。 dp[ 1][ 2 ]: 'b','a' -> 2。 dp[ 0][ 2 ]: 'a','b','a' 首先 dp[ 0][ 2] = dp[ 0][ 1 ]+1=3。 然后 k=0: target[ 0]=='a' 但 target[ 2]=='a' 相等,所以 dp[ 0][ 2] = min(3, dp[ 0][ -1] + dp[ 1][ 1 ]),注意边界。 实际上,当 k=0,区间分 [ l, k-1] 和 [ k+1, r-1] 即 [ 0,-1] 和 [ 1,1 ]。 dp[ 0][ -1] 表示空区间,操作数 0。dp[ 1][ 1]=1,所以总 1,但还需要一次操作打印 target[ 0] 和 target[ 2 ] 为 'a',所以是 1+1=2? 我们定义 dp[ l][ r] 为打印区间 [ l,r] 的最少次数,当 k=0 时,我们可以在一次操作打印 [ 0,2] 为 'a',然后处理 [ 1,1] 为 'b',所以总次数 = 1 + dp[ 1][ 1 ] = 2。 所以方程应该是: dp[ l][ r] = min(dp[ l][ r], dp[ l][ k-1] + 1 + dp[ k+1][ r-1 ])? 但 k=0 时,dp[ l][ k-1] 是空区间 0,dp[ k+1][ r-1] 是 [ 1,1 ] 即 1,再加 1 得 2。正确。 所以最终方程为: dp[ l][ r] = min(dp[ l][ r], dp[ l][ k-1] + 1 + dp[ k+1][ r-1]) if target[ k] == target[ r ] 且当 k+1 > r-1 时,dp[ k+1][ r-1 ] = 0。 按此计算,最终 dp[ 0][ 4 ] = 3。 代码框架 (Python) 总结 这道题是区间 DP 的经典应用,通过判断区间两端或中间相同颜色来减少操作次数。颜色种类有限的条件在标准解法中不影响状态转移,但若 k 极小,可用状态压缩进一步优化。核心是找到最优的第一次打印区间,使得相同颜色尽量一起打印,从而减少操作次数。