奇怪的打印机问题(最小打印次数问题)
字数 1283 2025-11-09 08:26:24
奇怪的打印机问题(最小打印次数问题)
我将为你详细讲解"奇怪的打印机"问题,这是一个经典的区间动态规划问题。
题目描述
有一个奇怪的打印机,它有以下特殊打印规则:
- 每次打印时,打印机可以选择打印任意字符
- 打印机每次可以在任意起始位置和结束位置打印同一字符
- 新打印的字符会覆盖之前同一位置上的字符
- 打印机每次只能打印连续的相同字符
给定一个字符串s,你的任务是找出打印机打印出该字符串所需的最少打印次数。
示例:
-
输入:s = "aaabbb"
-
输出:2
-
解释:首先打印"aaa",然后打印"bbb"
-
输入:s = "aba"
-
输出:2
-
解释:首先打印"aaa",然后在中间位置打印"b"覆盖掉第二个a
解题思路分析
这个问题可以用区间动态规划来解决。我们需要定义状态,找到状态转移方程,然后进行求解。
详细解题步骤
步骤1:定义状态
定义dp[i][j]表示打印字符串s从位置i到位置j的子串所需的最少打印次数。
步骤2:寻找基本情况和状态转移
基本情况:
- 当i = j时,只有一个字符,需要1次打印:dp[i][i] = 1
状态转移:
对于区间[i, j],我们有两种情况:
-
如果s[i] = s[j]
- 我们可以在打印s[i]时一次性打印到位置j,这样打印s[j]时就不需要额外次数
- 因此:dp[i][j] = dp[i][j-1]
-
如果s[i] ≠ s[j]
- 我们需要将区间[i, j]分割成两个子区间[i, k]和[k+1, j]
- 遍历所有可能的分割点k (i ≤ k < j)
- dp[i][j] = min(dp[i][k] + dp[k+1][j]),对所有k取最小值
步骤3:算法实现
def strangePrinter(s):
if not s:
return 0
n = len(s)
# 创建dp表,初始化为最大值
dp = [[float('inf')] * n for _ in range(n)]
# 基本情况:单个字符需要1次打印
for i in range(n):
dp[i][i] = 1
# 按区间长度从小到大计算
for length in range(2, n + 1): # 区间长度从2到n
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:首尾字符不同,遍历所有分割点
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]
步骤4:示例分析
以s = "aba"为例:
-
初始化dp表:
dp[0][0] = 1, dp[1][1] = 1, dp[2][2] = 1 其他位置为无穷大 -
计算长度为2的区间:
- [0,1]: s[0]='a' ≠ s[1]='b'
dp[0][1] = min(dp[0][0] + dp[1][1]) = 1 + 1 = 2 - [1,2]: s[1]='b' ≠ s[2]='a'
dp[1][2] = min(dp[1][1] + dp[2][2]) = 1 + 1 = 2
- [0,1]: s[0]='a' ≠ s[1]='b'
-
计算长度为3的区间[0,2]:
- s[0]='a' ≠ s[2]='a',但首尾相同!
- 根据规则:dp[0][2] = dp[0][1] = 2
-
最终结果:dp[0][2] = 2
步骤5:优化思路
上述算法的时间复杂度是O(n³),我们可以进行优化:
- 预处理优化:如果字符串中有连续相同的字符,可以压缩成一个字符
- 记忆化搜索:使用递归+记忆化的方式实现
- 状态定义优化:重新定义状态转移方程
优化后的代码:
def strangePrinter(s):
if not s:
return 0
# 预处理:合并连续相同字符
compressed = []
for char in s:
if not compressed or char != compressed[-1]:
compressed.append(char)
s = ''.join(compressed)
n = len(s)
dp = [[0] * n for _ in range(n)]
for i in range(n-1, -1, -1):
dp[i][i] = 1
for j in range(i+1, n):
if s[i] == s[j]:
dp[i][j] = dp[i][j-1]
else:
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]
总结
这个问题的关键在于理解:
- 当首尾字符相同时,可以节省一次打印操作
- 当首尾字符不同时,需要找到最优的分割点
- 区间动态规划按区间长度从小到大的顺序计算
通过这个例子,我们可以看到区间动态规划在解决这类"最优分割"问题上的强大能力。