Dynamic_Programming/1531.String-Compression-II/Readme.md
第一印象时我会这样设计DP方案:dp[i][k]表示前i个字母里删除k个所能得到的最优解(即最短的行程长度编码)。突破口应该是考虑第k个删除的字母的位置j在哪里,所以状态转移方程大致是:
dp[i][k] = max{dp[j-1][k-1] + s[j+1:i]的行程长度编码} for j=1,2,...i
但是我们在跑第二个例子 s = "aabbaa", k = 2 的时候会发现问题:我们删除了中间的bb,则第一段aa和最后一段的aa会拼接起来。而上述的表达式中完全没有考虑到拼接的处理。
换句话说,dp[j-1][k-1]对应的原码,和s[j+1:i]这段原码,其run-length encode不是简单的长度相加关系,而是有可能拼接在一起,形成更短的run-length encode. 为了便于处理这种“拼接关系”,我们需要提供更多的信息接口。所以会有以下的想法:令dp[i][k][ch][num]表示前i个字母、删除k个字母、最后的字母是ch、最后的字母连续出现的次数是num,所对应的最优解(即最短的行程长度编码)。
那么求解dp[i][k][ch][num]的关键是找到哪合法的前驱状态dp[?][?][?][?]可以转移到它。实际写代码的过程中,发现这样实现比较复杂。我们可以用另外一种DP的写法,就是看dp[i][k][ch][num]能够影响哪些后继节点。事实上,它直接影响的就是dp[i+1][?][?][?]的状态,我们只要讨论dp[i+1][?][?][?]的取值可能性即可。
dp[i][k][ch][num]+1,即run-length encode单纯地加上1. if (num==1) add = 1; // e.g: a -> a2
else if (num>=2 && num<=8) add = 0; // e.g: a3->a4
else if (num==9) add = 1; // e.g: a9->a10;
else if (num>=10) add = 0; // e.g: a10->a11;
最终的结果是要求在所有n个字母中删去K个字母,所以需要再dp[n][K][?][?]中挑选一个最小值。
另外需要注意的一个技巧是,我们不需要把第四个维度开到100,事实上num>=10之后,即使再拼接相同的字母,run-length encode也都不会再改变。所以我们可以把所有num>=10的状态都归为同一个状态,来节省空间的开辟。