LC 664. Strange Printer
https://leetcode.com/problems/strange-printer/
DP问题最重要的就是状态转移方程。这题我最开始考虑的状态转移方程如下:
- dp[i][j][c]. 打印从s[i..j], 并且之前打印的字符是c,
- dp[i][j][c]的状态转移方程是
- 我们先找到k, 其中s[i..k]这里面所有的字符都是s[i]
- 如果s[i] == c, 那么说明其实c可以直接打印到k这个位置,所以就是打印剩余字符了 dp[k+1][i][j][c]
- 如果s[i] != c的话,那么可以分为两段打印,假设 k<= k2 <= j + 1
- 1 + dp[k+1][k2-1][s[i]] 前面一段先用s[i]打印一次,然后解决s[k+1..k2-1]这段
- dp[k2][j][c] 剩余的还是使用字符c来打印
这样的时间复杂度是O(n^2 * 26 * n),空间复杂度是O(n^2 * 26). 用Python实现是超时的,但是使用Java实现没有问题。这个我在后面附上第一种解法。
事实证明是我想复杂了,讨论区里面有人给出了解法
第一种:dp[i][j] = 1 + dp[i + 1][j]. i单独打印, s[i + 1, j]段另外打印 第二种:dp[i][j] = min(dp[i][j], dp[i + 1][k] + dp[k + 1][j]); dp[i + 1][k]代表将i放到[i+ 1, k]一起打印,dp[k + 1][j]代表[k + 1, j]另外打印,(s[i] == s[k]) 第三种:dp[i][j] = min(dp[i][j], dp[i + 1][j]); dp[i + 1][j]代表将i放入[j + 1, i]一起打印(s[i] == s[j])
其实case3是case2的特殊情况,所以其实只要考虑两种。我觉得case2里面这点很重要 "将i放到[i+ 1, k]一起打印",这可以理解成某种逆向思维的解法: "因为s[k]==s[i],所以其实可以在某次打印s[k]的时候,顺带把s[i]打印了。"不过其实操作应该是先以s[i]打印s[i..k]这段。
这种逆向思维解法有个好处是,将打印计算的成本后移了,避免了去考虑"上次打印的字符"是什么这个问题。如果我们之前的考虑是"先以s[i]打印s[i..k]"这段的话, 那么很容易会写出代价是 1 + s[i+1..k-1], 不过这个是错误的,因为在s[i+1..k-1]里面还需要考虑上次打印的字符是什么。
这题状态转移方程有点奇怪,后面可以在继续考虑考虑。另外在解决问题之前,可以先对字符串做压缩将相同字符使用一个表示,比如"aaaabbbbc" -> "abc".
class Solution: def strangePrinter(self, s: str) -> int: if not s: return 0 p = s[0] ss = [] for c in s: if c != p: ss.append(p) p = c ss.append(p) print(''.join(ss)) import functools @functools.lru_cache(None) def fun(i, j): if i > j: return 0 ans = 1 + fun(i + 1, j) for k in range(i + 1, j + 1): if ss[i] == ss[k]: ans = min(ans, fun(i + 1, k) + fun(k + 1, j)) return ans ans = fun(0, len(ss) - 1) return ans
然后我在粘贴下第一种解法
import java.util.*; class Solution { int[][][] dp; public int query(char[] cs, int i, int j, int c) { if (i > j) { return 0; } int ans = dp[i][j][c]; if (ans != -1) { return ans; } ans = 1 << 30; int k = i; while ((k <= j) && (cs[k] == cs[i])) { k += 1; } if (cs[i] == c) { ans = query(cs, k, j, c); } else { for (int k2 = k; k2 <= j + 1; k2++) { int res = 1 + query(cs, k, k2 - 1, cs[i]) + query(cs, k2, j, c); ans = Math.min(ans, res); } } dp[i][j][c] = ans; return ans; } public int strangePrinter(String s) { int n = s.length(); if (n == 0) { return 0; } char[] cs = s.toCharArray(); for (int i = 0; i < cs.length; i++) { cs[i] -= 'a'; } dp = new int[n][n][26]; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { for (int c = 0; c < 26; c++) { dp[i][j][c] = -1; } } } int ans = query(cs, 0, n - 1, cs[0]) + 1; return ans; } }