奇怪的打印机的最小打印次数问题
字数 1341 2025-11-02 10:11:13
奇怪的打印机的最小打印次数问题
题目描述
有一台奇怪的打印机,它有两个特性:
- 每次打印时,打印机可以打印由同一个字符组成的连续序列
- 在每次打印过程中,打印机可以在已经打印好的字符上覆盖打印新的字符
给定一个字符串 s,你的任务是找出打印出该字符串所需的最少打印次数。
示例
-
输入:s = "aaabbb"
-
输出:2
-
解释:先打印 "aaa",然后打印 "bbb"
-
输入:s = "aba"
-
输出:2
-
解释:先打印 "aaa",然后在中间位置打印 'b' 覆盖掉 'a'
解题思路
这个问题可以使用区间动态规划来解决。我们需要找到打印整个字符串 s 的最小打印次数。
定义状态
定义 dp[i][j] 表示打印子串 s[i...j] 所需的最小打印次数。
状态转移方程
-
基础情况:当 i == j 时,dp[i][j] = 1(单个字符只需要打印一次)
-
一般情况:对于区间 [i, j]
- 如果 s[i] == s[j],那么我们可以先打印 s[i] 字符覆盖整个区间,然后再处理中间部分
- 如果 s[i] != s[j],那么我们需要找到最优的分割点
更具体地说:
- 如果 s[i] == s[j],那么 dp[i][j] = dp[i][j-1]
因为我们可以先打印 s[i] 覆盖整个区间,然后中间部分与处理 [i, j-1] 类似 - 如果 s[i] != s[j],那么我们需要在区间 [i, j] 中找到一个分割点 k,使得:
dp[i][j] = min(dp[i][k] + dp[k+1][j]),其中 i ≤ k < j
详细解题步骤
让我们通过示例 s = "aba" 来理解这个过程:
-
初始化:创建 dp 表格,对角线元素为 1
- dp[0][0] = 1, dp[1][1] = 1, dp[2][2] = 1
-
处理长度为 2 的子串:
- 子串 "ab":s[0] != s[1],dp[0][1] = min(dp[0][0] + dp[1][1]) = 1 + 1 = 2
- 子串 "ba":s[1] != s[2],dp[1][2] = min(dp[1][1] + dp[2][2]) = 1 + 1 = 2
-
处理长度为 3 的子串 "aba":
- s[0] == s[2] ('a' == 'a'),所以 dp[0][2] = dp[0][1] = 2
- 验证:先打印 "aaa",然后在位置 1 打印 'b'
算法实现
def strangePrinter(s: str) -> int:
n = len(s)
if n == 0:
return 0
# 创建 DP 表
dp = [[0] * n for _ in range(n)]
# 初始化:单个字符需要打印一次
for i in range(n):
dp[i][i] = 1
# 按子串长度从小到大处理
for length in range(2, n + 1):
for i in range(n - length + 1):
j = i + length - 1
# 情况1:首尾字符相同
if s[i] == s[j]:
dp[i][j] = dp[i][j-1]
else:
# 情况2:首尾字符不同,需要找到最优分割点
dp[i][j] = float('inf')
for k in range(i, j):
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j])
return dp[0][n-1]
复杂度分析
- 时间复杂度:O(n³),需要三层循环
- 空间复杂度:O(n²),用于存储 DP 表
优化思路
实际上,我们可以进一步优化状态转移方程。当 s[i] == s[j] 时,有更精确的表达式:
dp[i][j] = min(dp[i][k] + dp[k+1][j]),其中 k 满足 s[k] == s[i]
这样可以避免不必要的计算,但最坏情况下时间复杂度仍然是 O(n³)。
这个问题的关键在于理解:当首尾字符相同时,我们可以利用打印的覆盖特性来减少打印次数。