某大厂员工:我的人生很失败,赚了1000多万买房赔了,孩子成绩全班倒数,媳妇整天抱怨
我刷到一条吐槽:大厂哥们说自己很失败,赚一千多万买房买成了“精准站岗”;娃成绩全班倒数;媳妇天天念叨,家里跟开了循环日志一样,报错不停。
评论区也挺真实。有网友说:房子亏了就当交学费,别把自己也亏进去。
要我说呀,就是变量太多,线程还互相抢锁,当然崩。
这种帖子我爱看,省得我以为只有我在生活里踩坑。平台的好处也简单:一边吃瓜一边捡经验,顺手把焦虑降个温,回头还能少走几步弯路。
这个“奇怪的打印机”啊,我一看到题目就想起我们公司那台老打印机,心情好打一页,心情不好打一行,跟产品经理一样阴晴不定…
题目其实就一句话:一台打印机,每次只能选一种字符,把这字符打印到一段连续区间上(可以覆盖原来的),问最少打印几次,才能把目标字符串打出来。听着挺玄学的对吧。
我刚看到的时候,第一个反应就是:这不贪心嘛,从左到右,一个连续段一个次数,结果一算,直接寄。就跟之前我选 MySQL 还是 Postgres 的时候一样,以为简单,结果细节多得要命。
你想象一下:字符串 aba。 你要是按“连续段”来搞:
先打 aaa,三位都是a再在中间覆盖个 b两次搞定,看着还挺优雅。
这个例子就暴露出核心点了:后面打印是可以覆盖前面的,所以有时候提前“顺带”打印一点,将来就省一次。这个感觉就很像我们写 SQL 的时候顺手多查两列,以后别的功能就能直接复用,哈哈。
正常人一看这种有前后覆盖关系的,基本就得上区间 DP 了。脑子里先有个画面:dp[i][j] 表示把子串 s[i..j] 打完,最少需要几次。然后你盯着这个区间想:
最笨的方案:先把 s[i+1..j]打完,再单独打一遍s[i],所以有个初始值dp[i][j] = dp[i+1][j] + 1但如果中间有某个 k,s[k] == s[i],那就可以在打印右边那一段的时候,顺便把i位置给带上。 具体一点:先把s[i+1..k-1]打完,再一次性把i和k那边的同一个字符一起打了,后面k..j这一段就可以复用。所以转移是:dp[i][j] = min(dp[i][j], dp[i+1][k-1] + dp[k][j])
口水话少一点,上点 Java 代码,你们自己感受下节奏:
publicclassStrangePrinter{
publicintstrangePrinter(String s){
if (s == null || s.length() == 0) return0;
// 小优化:把连续相同字符压缩掉,"aaabbb" -> "ab"
StringBuilder sb = new StringBuilder();
char[] arr = s.toCharArray();
sb.append(arr[0]);
for (int i = 1; i < arr.length; i++) {
if (arr[i] != arr[i - 1]) {
sb.append(arr[i]);
}
}
char[] str = sb.toString().toCharArray();
int n = str.length;
int[][] dp = newint[n][n];
// 长度为1的区间只需要打印一次
for (int i = 0; i < n; i++) {
dp[i][i] = 1;
}
// len 是区间长度
for (int len = 2; len <= n; len++) {
for (int i = 0; i + len - 1 < n; i++) {
int j = i + len - 1;
// 先用最笨的方法:单独打一遍 str[i]
dp[i][j] = dp[i + 1][j] + 1;
// 尝试和后面某个相同字符合并
for (int k = i + 1; k <= j; k++) {
if (str[k] == str[i]) {
int left = (k == i + 1) ? 0 : dp[i + 1][k - 1];
dp[i][j] = Math.min(dp[i][j], left + dp[k][j]);
}
}
}
}
return dp[0][n - 1];
}
}
你看这个双层 for 再套一个 for,典型“区间 DP 模板脸”,但细看又有点小心机:
先把字符串压缩一下,把连续相同字符合成一个,这一步别小看,经常直接把复杂度从天花板拉回地面,和线上压测前先把日志关一关是一个道理。 dp[i][j]初始化用的是“把左边单独打一遍”的方案,这保证你不会比最笨还笨。然后枚举 k去薅羊毛:只要s[k] == s[i],就试一试能不能让i和k共用一次打印。
有朋友一看三层循环就开始头大:“这不得超时啊?”冷静一下,压缩后长度其实挺有限的,真实数据里连续相同字符很多,比你想象的短。再说了,刷题的时候怕啥超时,线上 SQL 扫全表都敢上,这点复杂度不算啥。
这个题写顺了之后,你会突然发现,很多那种“可以覆盖、可以合并”的问题,其实脑子里就该冒出一个区间 DP 的表格,然后慢慢填,别一上来就指针乱飞。行,我先去给办公室那台真·奇怪打印机清清纸盒,天天卡纸,比这题难多了。