leetcode双周赛149 T4(DP + 输出具体方案)
题目链接:problem
\(f[i][j]\): 将\(str[i]\)变为字符\(j\),将\(str[i到n]\)变为合法字符串,需要的最小操作次数。
\(nxt[i][j]\):将\(str[i]\)变为字符\(j\)的所有方案中,最优方案转移过来的字符。其中:
- \(nxt[i][j]==j\):\(f[i][j]\) 从 \(f[i+1][j]\) 转移而来(意味着最优方案下\(str[i]==j,且str[i+1]==j\))
- \(nxt[i][j]==k,k!=j\):\(f[i][j]\) 从 \(f[i+3][k]\) 转移而来(意味着最优方案下\(str[i]==str[i+1]==str[i+2]==j,str[i+3]==k\))
\(minj[i]\):将\(str[i到n]\)变为合法字符串的最优方案中,\(str[i]\)应该变为的字符。
\(ans = f[0][minj[0]]\)方案对应的字符串
\(trans\):
\[f[i][j] = min(f[i + 1][j] + cost[str[i]->j], \min_{k=0}^{25}f[i + 3][k] + cost[str[i]->j] + cost[str[i+1]->j] + cost[str[i+2]->j])
\]
注意当后者的\(k==j\)时,前者的最优方案一定不会比后者的最优方案更劣。因此可以直接枚举(\(continue\)掉也可以)。
按照该转移式\(dp\)后,最小代价已知。再根据\(minj[0]\)和\(nxt\)数组来还原最优方案对应的字符串,根据\(nxt\)数组的定义来还原即可。
复杂度:\(O(n*26*26)\)。还可以优化掉一个\(26\):每次计算完一轮\(f[i][]\)时,新开一个\(minf[i]\)来保存\(f[i][0到25]\)的最小值,这样就省去了最内层\(26\)的枚举。
代码:
class Solution {
#define inf 0x3f3f3f3f
int f[50010][26];
int nxt[50010][26];
int minj[50010];
public:
string minCostGoodCaption(string str) {
int n = str.size();
if(n < 3){
return "";
}
for(int i = n - 1; i >= 0; i--){
int mn = inf;
for(int j = 0; j < 26; j++){
int res1 = f[i + 1][j] + abs(str[i] - 'a' - j);
int res2 = inf;
if(i <= n - 6){
for(int k = 0; k < 26; k++){
res2 = min(res2, f[i + 3][k] + abs(str[i] - 'a' - j) + abs(str[i + 1] - 'a' - j)
+ abs(str[i + 2] - 'a' - j));
}
}
if(res2 < res1 || (res2 == res1 && minj[i + 3] < j)){
nxt[i][j] = minj[i + 3];
} else{
nxt[i][j] = j;
}
f[i][j] = min(res1, res2);
if(f[i][j] < mn){ // 保证了构造出的最优方案下的字符串的字典序最小
mn = f[i][j];
minj[i] = j;
}
}
}
string ans(n, 0);
int i = 0, j = minj[0];
while(i < n){
ans[i] = 'a' + j;
int k = nxt[i][j];
if(k == j){
i++;
}else{
ans[i + 2] = ans[i + 1] = ans[i];
i += 3;
j = k;
}
}
return ans;
}
};