奇怪的打印机问题
字数 1410 2025-11-03 00:20:06

奇怪的打印机问题

题目描述

有一台奇怪的打印机,它有两个特性:

  1. 每次打印时,只能打印连续相同字符的序列
  2. 每次打印可以在任意位置开始和结束,覆盖之前已经存在的字符

给定一个字符串 s,你的任务是找出打印机打印出该字符串所需的最少打印次数。

示例

  • 输入:s = "aaabbb"

  • 输出:2

  • 解释:先打印 "aaa",然后打印 "bbb"

  • 输入:s = "aba"

  • 输出:2

  • 解释:先打印 "aaa",然后在中间位置打印 "b" 覆盖第二个 'a'

解题思路

这个问题可以使用区间动态规划来解决。我们需要找到打印整个字符串 s 的最少次数。

状态定义
定义 dp[i][j] 表示打印子串 s[i...j] 所需的最少打印次数。

状态转移方程

  1. 基础情况:当区间长度为 1 时,dp[i][i] = 1

  2. 一般情况:对于区间 [i, j],我们考虑:

    • 如果 s[i] == s[j]:我们可以先打印整个区间为 s[i] 字符,然后再处理中间部分
    • 如果 s[i] != s[j]:我们需要将区间分割成两部分分别处理

具体的状态转移:

  • 情况1:s[i] == s[j]
    dp[i][j] = min(dp[i][j-1], dp[i+1][j])
    解释:当首尾字符相同时,打印最后一个字符时可以顺便打印第一个字符,或者打印第一个字符时顺便打印最后一个字符

  • 情况2:s[i] != s[j]
    dp[i][j] = min(dp[i][k] + dp[k+1][j]),其中 i ≤ k < j
    解释:当首尾字符不同时,我们需要找到最优的分割点,将区间分成两部分分别处理

详细解题步骤

让我们通过示例 s = "aba" 来详细说明:

步骤1:初始化

  • dp[0][0] = 1 (打印 "a")
  • dp[1][1] = 1 (打印 "b")
  • dp[2][2] = 1 (打印 "a")

步骤2:处理长度为2的区间

  • 区间 [0,1]:"ab"

    • s[0] != s[1],所以 dp[0][1] = min(dp[0][0] + dp[1][1]) = 1 + 1 = 2
  • 区间 [1,2]:"ba"

    • s[1] != s[2],所以 dp[1][2] = min(dp[1][1] + dp[2][2]) = 1 + 1 = 2

步骤3:处理长度为3的区间

  • 区间 [0,2]:"aba"
    • s[0] == s[2] ('a' == 'a')
    • dp[0][2] = min(dp[0][1], dp[1][2]) = min(2, 2) = 2

最终结果:dp[0][2] = 2

算法实现要点

  1. 按区间长度从小到大处理
  2. 对于每个区间 [i, j],根据首尾字符是否相同选择不同的转移策略
  3. 时间复杂度:O(n³),空间复杂度:O(n²)

优化思路

实际上,当 s[i] == s[j] 时,我们可以有更优的策略:dp[i][j] = dp[i][j-1]
因为我们可以先打印整个区间为 s[i] 字符,然后只处理中间不同的部分,这样通常比分割区间更优。

这个问题的关键在于理解:当首尾字符相同时,我们可以在一次打印中完成首尾字符的打印,从而减少打印次数。

奇怪的打印机问题 题目描述 有一台奇怪的打印机,它有两个特性: 每次打印时,只能打印连续相同字符的序列 每次打印可以在任意位置开始和结束,覆盖之前已经存在的字符 给定一个字符串 s,你的任务是找出打印机打印出该字符串所需的最少打印次数。 示例 输入:s = "aaabbb" 输出:2 解释:先打印 "aaa",然后打印 "bbb" 输入:s = "aba" 输出:2 解释:先打印 "aaa",然后在中间位置打印 "b" 覆盖第二个 'a' 解题思路 这个问题可以使用区间动态规划来解决。我们需要找到打印整个字符串 s 的最少次数。 状态定义 定义 dp[ i][ j] 表示打印子串 s[ i...j ] 所需的最少打印次数。 状态转移方程 基础情况 :当区间长度为 1 时,dp[ i][ i ] = 1 一般情况 :对于区间 [ i, j ],我们考虑: 如果 s[ i] == s[ j]:我们可以先打印整个区间为 s[ i ] 字符,然后再处理中间部分 如果 s[ i] != s[ j ]:我们需要将区间分割成两部分分别处理 具体的状态转移: 情况1:s[ i] == s[ j ] dp[ i][ j] = min(dp[ i][ j-1], dp[ i+1][ j ]) 解释:当首尾字符相同时,打印最后一个字符时可以顺便打印第一个字符,或者打印第一个字符时顺便打印最后一个字符 情况2:s[ i] != s[ j ] dp[ i][ j] = min(dp[ i][ k] + dp[ k+1][ j]),其中 i ≤ k < j 解释:当首尾字符不同时,我们需要找到最优的分割点,将区间分成两部分分别处理 详细解题步骤 让我们通过示例 s = "aba" 来详细说明: 步骤1:初始化 dp[ 0][ 0 ] = 1 (打印 "a") dp[ 1][ 1 ] = 1 (打印 "b") dp[ 2][ 2 ] = 1 (打印 "a") 步骤2:处理长度为2的区间 区间 [ 0,1 ]:"ab" s[ 0] != s[ 1],所以 dp[ 0][ 1] = min(dp[ 0][ 0] + dp[ 1][ 1 ]) = 1 + 1 = 2 区间 [ 1,2 ]:"ba" s[ 1] != s[ 2],所以 dp[ 1][ 2] = min(dp[ 1][ 1] + dp[ 2][ 2 ]) = 1 + 1 = 2 步骤3:处理长度为3的区间 区间 [ 0,2 ]:"aba" s[ 0] == s[ 2 ] ('a' == 'a') dp[ 0][ 2] = min(dp[ 0][ 1], dp[ 1][ 2 ]) = min(2, 2) = 2 最终结果 :dp[ 0][ 2 ] = 2 算法实现要点 按区间长度从小到大处理 对于每个区间 [ i, j ],根据首尾字符是否相同选择不同的转移策略 时间复杂度:O(n³),空间复杂度:O(n²) 优化思路 实际上,当 s[ i] == s[ j] 时,我们可以有更优的策略:dp[ i][ j] = dp[ i][ j-1 ] 因为我们可以先打印整个区间为 s[ i ] 字符,然后只处理中间不同的部分,这样通常比分割区间更优。 这个问题的关键在于理解:当首尾字符相同时,我们可以在一次打印中完成首尾字符的打印,从而减少打印次数。