有台奇怪的打印机有以下两个特殊要求:
打印机每次只能打印由 同一个字符 组成的序列。
每次可以在从起始到结束的任意位置打印新字符,并且会覆盖掉原来已有的字符。
给你一个字符串 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,i] 就是刷ii自己,相当于1步加上剩下的f[i+1,j]这一段。其他的情况假设刷到了k,那么后面部分就是f[k + 1,j]和前面没有关系,关键是前面部分怎么求,这里从i刷到k和从i刷到k-1的操作次数是一样的,因为对于f[i,k-1],相当于刷到了中间某个位置,但是我可以假设第一次刷到了最后,反正后半段会被覆盖掉。这样就可以类比出来。注意这里需要i和k的元素相等。还是比较抽象的
class Solution {
public:
int strangePrinter(string s) {
int n = s.size();
vector<vector<int>> f(n + 1, vector<int>(n + 1));
for(int len = 1; len <= n; len ++ )
for(int i = 0; i + len - 1 < n; i ++ ) {
int j = i + len - 1;
f[i][j] = 1 + f[i + 1][j];
for(int k = i + 1; k <= j; k ++ )
if(s[i] == s[k])
f[i][j] = min(f[i][j], f[i][k - 1] + f[k + 1][j]);
}
return f[0][n - 1];
}
};
这道题是一个典型的动态规划问题,我们要解决的目标是找到最少的打印次数,使得可以打印出给定的字符串 s
。
问题分析
打印机每次只能打印一个相同字符组成的序列,并且可以覆盖掉已有字符。我们需要利用这个特性,通过动态规划来找到最优解。
动态规划解法
我们定义一个二维数组 f
,其中 f[i][j]
表示打印子串 s[i...j]
需要的最少打印次数。
状态转移
- 初始化:
f[i][i] = 1
,因为打印单个字符只需要一次打印。 - 转移方程:
- 对于任意子串
s[i...j]
,我们可以将其看作s[i]
和s[i+1...j]
两部分,则f[i][j] = 1 + f[i + 1][j]
。 - 如果我们找到一个
k
,使得s[i] == s[k]
,我们可以将s[i...j]
分成两部分s[i...k]
和s[k+1...j]
,那么f[i][j]
的值可以更新为f[i][k - 1] + f[k + 1][j]
。
- 对于任意子串
代码解析
class Solution {
public:
int strangePrinter(string s) {
int n = s.size();
// 初始化一个二维动态规划数组
vector<vector<int>> f(n + 1, vector<int>(n + 1, 0));
// 从子串长度为1开始,逐渐增加长度
for (int len = 1; len <= n; len++) {
for (int i = 0; i + len - 1 < n; i++) {
int j = i + len - 1;
f[i][j] = 1 + f[i + 1][j]; // 最坏情况,f[i...j]需要1次打印+f[i+1...j]
// 检查是否可以通过中间的某个相同字符来优化
for (int k = i + 1; k <= j; k++) {
if (s[i] == s[k]) {
f[i][j] = min(f[i][j], f[i][k - 1] + f[k + 1][j]);
}
}
}
}
return f[0][n - 1];
}
};
详细讲解
-
初始化动态规划数组:
vector<vector<int>> f(n + 1, vector<int>(n + 1, 0));
这里我们创建了一个大小为
(n+1) x (n+1)
的二维数组f
,并初始化所有值为0
。这个数组用于存储子问题的解。 -
遍历子串长度:
for (int len = 1; len <= n; len++) {
我们从子串长度
1
开始遍历,一直到整个字符串长度n
。 -
遍历子串的起始位置:
for (int i = 0; i + len - 1 < n; i++) {
对于每一个子串长度,我们从起始位置
i
遍历到可以形成该长度的末尾位置。 -
确定子串的结束位置:
int j = i + len - 1;
-
初始情况:
f[i][j] = 1 + f[i + 1][j];
这表示直接打印子串
s[i...j]
需要至少1
次加上打印s[i+1...j]
的次数。 -
优化情况:
for (int k = i + 1; k <= j; k++) { if (s[i] == s[k]) { f[i][j] = min(f[i][j], f[i][k - 1] + f[k + 1][j]); } }
这里我们检查从
i
到j
的子串中是否有其他位置k
使得s[i] == s[k]
。如果存在,我们可以将s[i...j]
分成两部分s[i...k]
和s[k+1...j]
来优化打印次数。
总结
通过动态规划的方法,我们将复杂问题分解为更小的子问题,逐步求解最终的答案。利用二维数组 f
来存储每个子问题的解,并根据条件进行优化,可以有效地求解最少打印次数的问题。