算法学习---让字符串成为回文串的最少插入次数
给你一个字符串 s ,每一次操作你都可以在字符串的任意位置插入任意字符。
请你返回让 s 成为回文串的 最少操作次数 。
「回文串」是正读和反读都相同的字符串。
leetCode题目链接:https://leetcode-cn.com/problems/minimum-insertion-steps-to-make-a-string-palindrome
做过最长回文子序列的话,看到题目第一时间应该想到找出最长回文子序列,剩余的字符就是必要插入的字符。这也是leetCode官方给出的第一种解法,详细可以到上面的链接中查看官方题解,这里就不多做描述了。
作为一个小白博主,并没有做过最长回文子序列,第一个想法是,设 i 为0, j 为s的最后一项,然后比较两者如果不同就往后插入字符,相同则i + 1 , j - 1
例如:s = "leetcode"时,
- leetcode => leetcodel
- leetcode => leetcodel
- leetcode => leetcodeel
- leetcode => leetcodteel
- leetcode => leetcodcteel
- leetcode => leetcodocteel
这显然是一个最优的情况,但是会有一些有问题的情况,需要后面的字符向前插入
例如:s = “zjveiiwvc”
首先按照只往后插入字符,需要插入8次:
- zjveiiwvc => zjveiiwvcz
- zjveiiwvc => zjveiiwvcjz
- zjveiiwvc => zjveiiwvcvjz
- zjveiiwvc => zjveiiwvcevjz
- zjveiiwvc => zjveiiwvcievjz
- zjveiiwvc => zjveiiwvciievjz
- zjveiiwvc => zjveiiwvcwiievjz
- zjveiiwvc => zjveiiwvcvwiievjz
最优情况:只需插入5次
- zjveiiwvc => zjveiiwvcz
- zjveiiwvc => zjveiiwvcjz
- zjveiiwvc => zjcveiiwvcjz
- zjveiiwvc => zjcvweiiwvcjz
- zjveiiwvc => zjcvweiiewvcjz
我们总结上述情况,之后发现,这其实是一个区间动归的一个过程
我们从上述情况总结出以下的状态转移方程
当s[i]不等于s[j]时,取 左插 或 右差 的最小值
if s[i] != s[j]
dp[i][j] = min(dp[i + 1][j] + 1, dp[i][j - 1] + 1)
当s[i]等于s[j]时,缩小边界
if s[i] == s[j]
dp[i][j] = min(dp[i + 1][j] + 1, dp[i][j - 1] + 1,dp[i + 1][j - 1])
那么从状态转移方程中我们看到,在进行计算时, 需要dp[i + 1][j] 、dp[i][j - 1] 、dp[i + 1][j - 1] , 因此计算的顺序极为重要
然而由于我们还无法确定最终得出的回文串的中心,因此我们需要先将所有小的区间全部计算一遍
代码如下
class Solution {
public:
int minInsertions(string s) {
int len = s.size();
vector<vector<int>> dp(len, vector<int>(len));
for (int dt = 1; dt < len; ++dt) //将dt理解为i与j区间的差值
{
for (int i = 0; i < len - dt; ++i)
{
int j = i + dt;
dp[i][j] = min(dp[i + 1][j], dp[i][j - 1]) + 1;
if(s[i] == s[j])
dp[i][j] = min(dp[i + 1][j - 1], dp[i][j]);
}
}
return dp[0][len - 1];
}
};