题目地址:
https://leetcode.com/problems/minimum-insertion-steps-to-make-a-string-palindrome/
给定一个长 n n n的字符串 s s s,允许在任意位置插入任意字符,问至少插入多少次可以使得 s s s成为回文串。
思路是动态规划。设 f [ l ] [ r ] f[l][r] f[l][r]是 s [ l : r ] s[l:r] s[l:r]需要变成回文串至少需要多少次插入,则我们需要返回 f [ 0 ] [ n − 1 ] f[0][n-1] f[0][n−1]。那么当 l = r l=r l=r时 f [ l ] [ r ] = 0 f[l][r]=0 f[l][r]=0,一个字母自己就是回文串;如果 l = r − 1 l=r-1 l=r−1,那么就看 s [ l ] = s [ r ] s[l]=s[r] s[l]=s[r]是否成立,如果是,则不需要插入, f [ l ] [ r ] = 0 f[l][r]=0 f[l][r]=0,否则显然 f [ l ] [ r ] = 1 f[l][r]=1 f[l][r]=1。对于其余情况,我们考虑如何递推。如果 s [ l ] = s [ r ] s[l]=s[r] s[l]=s[r],那么只需要让 s [ l + 1 : r − 1 ] s[l+1:r-1] s[l+1:r−1]变成回文串就行了,那么此时 f [ l ] [ r ] = f [ l + 1 ] [ r − 1 ] f[l][r]=f[l+1][r-1] f[l][r]=f[l+1][r−1](严格证明亦不难,如果某种最优方案能使得 s s s变成回文串,那么最终的回文串必然以 s [ l ] s[l] s[l]开头和结尾,否则的话就说明在两边添加了多余的字符,这是不必要的;那么既然以 s [ l ] s[l] s[l]开头和结尾,任意的插入操作都可以视为是对 s [ l + 1 : r − 1 ] s[l+1:r-1] s[l+1:r−1]的插入操作,那么就可以用归纳假设了,至少需要 f [ l + 1 ] [ r − 1 ] f[l+1][r-1] f[l+1][r−1]步);如果 s [ l ] ≠ s [ r ] s[l]\ne s[r] s[l]=s[r],那么最优方案得到的最终的回文串不能同时以 s [ l ] s[l] s[l]做开头与以 s [ r ] s[r] s[r]做结尾,但是必须或者以 s [ l ] s[l] s[l]做开头,或者以 s [ r ] s[r] s[r]做结尾,否则的话说明在两边添加多余字符了,矛盾,既然如此,如果以 s [ l ] s[l] s[l]开头,那么必然是先将 s [ l + 1 : r ] s[l+1:r] s[l+1:r]变成了回文串,然后在后面加一个 s [ r ] s[r] s[r](当然顺序可能不是如此,但无论怎样一定会在最后添加一个 s [ l ] s[l] s[l],我们调整插入顺序使得这一步最后做,那么前面的步骤其实就是在使得 s [ l + 1 : r ] s[l+1:r] s[l+1:r]变成回文串),所以此时 f [ l ] [ r ] = min { f [ l + 1 ] [ r ] , f [ l ] [ r − 1 ] } + 1 f[l][r]=\min\{f[l+1][r],f[l][r-1]\}+1 f[l][r]=min{f[l+1][r],f[l][r−1]}+1。综上: f [ l ] [ r ] = { f [ l + 1 ] [ r − 1 ] , s [ l ] = s [ r ] min { f [ l + 1 ] [ r ] , f [ l ] [ r − 1 ] } + 1 , s [ l ] ≠ s [ r ] f[l][r]=\begin{cases}f[l+1][r-1],s[l]=s[r]\\\min\{f[l+1][r],f[l][r-1]\}+1,s[l]\ne s[r] \end{cases} f[l][r]={f[l+1][r−1],s[l]=s[r]min{f[l+1][r],f[l][r−1]}+1,s[l]=s[r]代码如下:
public class Solution {
public int minInsertions(String s) {
int n = s.length();
int[][] f = new int[n][n];
for (int len = 2; len <= n; len++) {
for (int l = 0; l + len - 1 < n; l++) {
int r = l + len - 1;
char ch1 = s.charAt(l), ch2 = s.charAt(r);
if (len == 2) {
f[l][r] = ch1 == ch2 ? 0 : 1;
} else {
f[l][r] = ch1 == ch2 ? f[l + 1][r - 1] : Math.min(f[l + 1][r], f[l][r - 1]) + 1;
}
}
}
return f[0][n - 1];
}
}
时空复杂度 O ( n 2 ) O(n^2) O(n2)。
C++:
class Solution {
public:
int minInsertions(string s) {
int n = s.size();
int f[n][n];
memset(f, 0, sizeof f);
for (int len = 2; len <= n; len++)
for (int l = 0; l + len - 1 < n; l++) {
int r = l + len - 1;
if (s[l] == s[r]) f[l][r] = f[l + 1][r - 1];
else f[l][r] = min(f[l + 1][r], f[l][r - 1]) + 1;
}
return f[0][n - 1];
}
};
时空复杂度一样。