Files
python/TangDou/CSP-J/J2-2022/力扣 664. 奇怪的打印机.md

157 lines
4.6 KiB
Markdown
Raw Normal View History

2025-08-30 18:35:01 +08:00
## 力扣 $664$. 奇怪的打印机
**题目描述**
有台奇怪的打印机有以下两个特殊要求:
打印机每次只能打印由 **同一个字符** 组成的序列。
每次可以在任意起始和结束位置打印新字符,并且会覆盖掉原来已有的字符。
给你一个字符串 $s$ ,你的任务是计算这个打印机打印它需要的最少打印次数。
**示例 1**:
> 输入:$s = "aaabbb"$
输出:$2$
解释:首先打印 $"aaa"$ 然后打印 $"bbb"$。
**示例 2:**
> 输入:$s = "aba"$
输出:$2$
解释:首先打印 $"aaa"$ 然后在第二个位置打印 $"b"$ 覆盖掉原来的字符 $'a'$。
**提示:**
* $1 <= s.length <= 100$
* $s$ 由小写英文字母组成
#### 解决方案 : 动态规划
我们可以使用 **动态规划** 解决本题
### 一、状态表示
令 $f[i][j]$ 表示打印完成区间 $[i,j]$ 的 **最少操作数**
#### 状态设计原则
* 状态设计是不是最终能包含答案
* 通过简单枚举可以找出正确答案
### 二、状态转移
当我们尝试计算出 $f[i][j]$ 时,需要考虑两种情况:
* $\large s[i] = s[j]$
即 **区间两端的字符相同时**,那么当我们打印左侧字符 $s[i]$ 时,可以顺便打印右侧字符 $s[j]$,这样我们即可忽略右侧字符对该区间的影响,只需要考虑如何尽快打印完区间 $[i,j - 1]$ 即可,即此时有
$$\large f[i][j] = f[i][j-1]$$
* $\large s[i] \neq s[j]$
即区间两端的字符不同,那么我们 **需要分别完成该区间的左右两部分的打印**。我们记两部分分别为区间 $[i,k]$ 和区间 $[k+1,j]$ (其中$\large i<=k<j$),此时
$$\large f[i][j]=\min_{k=i}^{j-1}(f[i][k]+f[k+1][j])$$
<font color='red' size=3><b>注意:这里$k$的取值范围值得仔细思考,因为是划分成两段,设第一段结束点为$k$,第二段的开始点为$k+1$,则有$k>=i,k+1<=j$,即:$i<=k<j$</b></font>
<font color='blue' size=4><b>
$i==k$时是成立的,表示只有$i$点打印一次,从$k+1$开始至$j$打印其它的(不一定是同一种噢~)
</b></font>
**总结状态转移方程为:**
$$\large \displaystyle f[i][j]=
\left\{\begin{matrix}
f[i][j-1] & s[i]=s[j] \\
\displaystyle \min_{k=i}^{j-1}f[i][k]+f[k+1][j] & s[i] \neq s[j]
\end{matrix}\right.
$$
### 三、边界与答案
* 边界条件为 $f[i][i] = 1$,对于长度为 $1$ 的区间,最少打印 $1$ 次
* 最后的答案为$f[0][n - 1]$
### 四、枚举顺序
注意到 $f[i][j]$ 的计算需要用到 $f[i][k]$ 和 $f[k + 1][j]$
$$\large \large i<=k<j$$
**为了保证动态规划的计算过程满足无后效性**,在实际代码中,我们需要 <font color='red' size=4><b>改变动态规划的计算顺序,从大到小地枚举 $i$,并从小到大地枚举 $j$,</b></font>这样可以保证当计算 $f[i][j]$ 时, $f[i][k]$ 和 $f[k + 1][j]$ 都已经被计算过。
### 五、复杂度分析
时间复杂度:$O(n^3)$,其中 $n$ 是字符串的长度。
空间复杂度:$O(n^2)$,其中 $n$ 是字符串的长度。我们需要保存所有 $n^2$ 个状态。
### 六、动态规划代码
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
/*
https://leetcode.cn/problems/strange-printer/
输入:s = "aaabbb"
输出:2
解释:首先打印 "aaa" 然后打印 "bbb"。
示例 2:
输入:s = "aba"
输出:2
解释:首先打印 "aaa" 然后在第二个位置打印 "b" 覆盖掉原来的字符 'a'。
*/
int f[N][N];
int main() {
string s;
cin >> s;
int n = s.size();
memset(f, 0x3f, sizeof f);
for (int i = n - 1; i >= 0; i--) {
f[i][i] = 1;
for (int j = i + 1; j < n; j++) {
if (s[i] == s[j])
f[i][j] = f[i][j - 1];
else {
for (int k = i; k < j; k++)
f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j]);
}
}
}
cout << f[0][n - 1] << endl;
return 0;
}
```
### 七、记忆化搜索代码
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
const int INF = 0x3f3f3f3f;
string s;
int f[N][N];
int dfs(int l, int r) {
if (l == r) return 1;
if (f[l][r]) return f[l][r];
int res = INF;
if (s[l] == s[r]) return dfs(l, r - 1);
for (int i = l; i < r; i++)
res = min(res, dfs(l, i) + dfs(i + 1, r));
return f[l][r] = res;
}
int main() {
cin >> s;
cout << dfs(0, s.size() - 1) << endl;
return 0;
}
```