奇怪的打印机 II(进阶版)
字数 1058 2025-10-30 21:15:36
奇怪的打印机 II(进阶版)
题目描述
有一个奇怪的打印机,它打印字符串的规则如下:
- 每次操作,打印机可以打印任意长度的相同字符序列
- 打印的新字符会覆盖之前同一位置上已经存在的字符
- 打印机最初有一个空白的打印区域(可以看作是无限长的空白字符串)
- 给定目标字符串s,求打印出目标字符串所需的最少操作次数
例如,对于字符串 "abacaba":
- 最少需要4次操作:先打印aaaaaaa,然后打印abbbbba,再打印abacaca,最后打印abacaba
解题思路
这个问题是"奇怪的打印机"问题的进阶版本,需要更精细的区间划分策略。我们采用区间动态规划来解决。
详细解题步骤
步骤1:定义状态
定义dp[i][j]表示打印出子串s[i...j]所需的最少操作次数。
步骤2:状态转移分析
考虑区间[i, j]的打印策略:
-
基础情况:当i = j时,dp[i][j] = 1(只需要一次操作打印单个字符)
-
一般情况:对于区间[i, j],有两种策略:
- 策略A:将s[i...j]分成两部分[i, k]和[k+1, j]分别打印
- 策略B:如果s[i] = s[k](其中i < k ≤ j),可以先打印整个区间为s[i]字符,然后再处理内部细节
步骤3:状态转移方程
dp[i][j] = min( dp[i][k] + dp[k+1][j] ) 对于所有i ≤ k < j
如果s[i] = s[j],有更优策略:
dp[i][j] = min(dp[i][j], dp[i][j-1])
解释:当首尾字符相同时,可以在打印首字符时顺便打印尾字符。
更精细的优化:
如果存在k使得s[i] = s[k],那么:
dp[i][j] = min(dp[i][j], dp[i][k-1] + dp[k][j] - 1)
因为s[i]和s[k]相同,可以在同一次操作中打印。
步骤4:完整算法实现
def strangePrinterII(s):
if not s:
return 0
n = len(s)
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
# 初始化为分割策略的最小值
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])
# 如果首尾字符相同,有更优策略
if s[i] == s[j]:
dp[i][j] = min(dp[i][j], dp[i][j-1])
# 寻找中间相同字符的优化机会
for k in range(i + 1, j + 1):
if s[i] == s[k]:
dp[i][j] = min(dp[i][j], dp[i][k-1] + (dp[k][j] if k <= j else 0))
return dp[0][n-1]
步骤5:示例分析
以字符串 "abacaba" 为例:
- 初始状态:所有长度为1的子串需要1次操作
- 计算"ab":dp[0][1] = min(dp[0][0]+dp[1][1]) = 2
- 计算"aba":
- 分割策略:min("a"+"ba", "ab"+"a") = min(1+2, 2+1) = 3
- 优化策略:s[0]='a'=s[2],dp[0][2] = dp[0][1] = 2
- 逐步计算得到最终结果:dp[0][6] = 4
步骤6:算法优化
可以进一步优化,当s[i] = s[k]时:
dp[i][j] = min(dp[i][j], dp[i][k-1] + dp[k+1][j])
因为s[i]和s[k]可以在同一次操作中完成。
时间复杂度:O(n³),空间复杂度:O(n²)
这个算法通过精细的区间划分和字符匹配优化,有效减少了打印操作次数,是区间动态规划的经典应用。