有关回文串的问题可以用很多种算法来解决,比如:专门为解决回文串而设计的算法Manacher 算法 ,马拉车算法等等,可以看这里 。而本文只关心用动态规划的方法来解决。
文章涉及到 求
最长回文子串 leetcode 5
最长回文子序列 leetcode 516
最少分割次数使得都为回文子串 leetcode 132
最少插入次数使得为回文子串 leetcode 1312
对待回文串问题的动态规划状态定义,一般有两个
- 一维数组 dp[i] 表示s[0…i]要形成回文子串的最少分割次数(这个定义还是比较少用的)
- 二维数组 dp[i] [j] 表示 s[i…j]之间是否是回文串/最长回文子序列的长度 等等
对于状态的定义要和题目所求的量搭上关系
状态转移方程思考也有两个途径
- 最常用的一种就是中心扩散的方法,即知道dp[i+1][j-1]的结果来求dp[i][j] (一般都是从左往右 从下往上推)
- 第二种就是顺序的求解 dp[i] 与 dp[i-1]搭上关系进行求解
1.最长回文子串 leetcode 5
状态定义
dp[i][j] 表示字符串 s 的第 i 到 j 个字母组成的串 s[i…j]是否为回文串
状态转移方程
dp[i][j] = dp[i+1][j-1] && (s[i]==s[j])
Base Case
i == j 时 dp[i][j] = 1
class Solution {
public:
string longestPalindrome(string s) {
int n = s.size();
string ans = "";
vector< vector<int> > dp( n, vector<int>(n,0) );
for( int l = 0; l < n; l ++ ){
for( int i = 0; i + l < n; i ++ ){
int j = i + l;
if( l == 0 ) dp[i][j] = 1;
else if( l == 1 ) dp[i][j] = (s[i] == s[j]);
else
dp[i][j] = dp[i+1][j-1] && (s[i] == s[j]);
if( dp[i][j] == 1 && (j-i+1) > ans.size() )
ans = s.substr( i, (j-i+1) );
}
}
return ans;
}
};
2.最长回文子序列 leetcode 516
状态定义
dp[i][j]表示在子串 s[i…j] 中,最长回文子序列的长度为 dp[i][j]
状态转移方程
if (s[i] == s[j])
dp[i][j] = dp[i + 1][j - 1] + 2; // s[i] s[j]都在最长回文子序列中
else
dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]); // s[i+1..j] 和 s[i..j-1] 谁的回文子序列更长?
Base Case
i == j 时 dp[i][j] = 1
class Solution {
public:
int longestPalindromeSubseq(string s) {
int n = s.size();
vector< vector<int> > dp( n, vector<int>(n, 0) );
for( int i = 0; i < n ; i ++ ){
dp[i][i] = 1;
}
for( int i = n-1; i >= 0; i -- ){
for( int j = i + 1; j < n ; j ++ ){
if( s[i] == s[j] )
dp[i][j] = dp[i+1][j-1] + 2;
else
dp[i][j] = max( dp[i][j-1], dp[i+1][j] );
}
}
return dp[0][n-1];
}
};
3.最少分割次数使得都为回文子串 leetcode 132
这道题的上一题leetcode131 求所有的分割方法,使用回溯法来解决。动态规划就是需要穷举出所有答案再求最值,回溯法只是穷举出答案,这道题要求最小分割次数,所以用动态规划来求解。
并且这里分割,很明显不能使用中心扩散的方法,所以只能使用一维dp
状态定义
dp[i]:表示前缀子串 s[0…i] 分割成若干个回文子串所需要最小分割次数。
状态转移方程
if (s[0:i] 本身就是一个回文串)
那么不用分割 dp[i] = 0
else
for j in (0..i)
if (s[j + 1, i] 是回文])
dp[i] = min( dp[i], [dp[j] + 1 )
Base Case
单个字符一定是回文串 dp[0] = 0;
class Solution {
private:
bool isPalindrome( const string &s, int l, int r ){
while( l <= r ){
if( s[l] != s[r] ) return false;
l ++; r --;
}
return true;
}
public:
int minCut(string s) {
int n = s.size();
vector<int> dp(n,n);
dp[0] = 0;
for( int i = 1; i < n ; i ++ ){
if( isPalindrome(s, 0, i) )
dp[i] = 0;
else{
for( int j = 0; j+1 <= i; j ++ ){
if( isPalindrome( s, j+1, i ) )
dp[i] = min( dp[i], dp[j] + 1);
}
}
}
return dp[n-1];
}
};
4.最少插入次数使得为回文子串 leetcode 1312
状态定义
dp[i][j] 表示s[i…j],最少需要进行dp[i][j]次插入才能变成回文串
状态转移方程
if(s[i] == s[j])
dp[i][j] = dp[i+1][j-1];
else
dp[i][j] = min( dp[i+1][j], dp[i][j-1] ) + 1;
Base Case
i == j时 dp[i][j] = 0
class Solution {
public:
int minInsertions(string s) {
int n = s.size();
vector< vector<int> > dp( n, vector<int>(n, 0) );
for( int i = n-1; i >= 0; i -- ){
for( int j = i+1; j < n; j ++ ){
if( s[i] == s[j] )
dp[i][j] = dp[i+1][j-1];
else
dp[i][j] = min( dp[i][j-1], dp[i+1][j] ) + 1;
}
}
return dp[0][n-1];
}
};