Basics on Dynamic Programming(DP) 5 - Palindrome
字符串处理,Palindrome
Given a string s, partition s such that every substring of the partition is a palindrome.
Return the minimum cuts needed for a palindrome partitioning of s.
For example, given s = "aab",
Return 1 since the palindrome partitioning ["aa","b"] could be produced using 1 cut.
state: f[i] “前i”个字符,用最少几次cut能分割为若干回文子串
function: f[i] = min{f[j] + 1, j < i && j+1 ~ i 是一个回文子串 }(找“最后”一次cut的位置) ,粗体部分也是DP可以用二维数组记录一下
intialize: f[i] = i-1
answer: f[s.length()],因为第0个留出来了
//所以需DP的方法 时间复杂度 O(n^2),空间复杂度 O(n^2)
int minCut ( string s ) {
int n = s . size ();
vector < vector < bool > > isPalindrome ( n , vector < bool > ( n , false )); //都是false
vector < int > dp ( n + 1 , 0 ); //"前i"个字符,用最少几次cut能分割为若干回文子串
// initialize
for ( int i = 0 ; i <= n ; i ++ ) dp [ i ] = i - 1 ; //第一个f[0]=-1,f1=0
for ( int i = 1 ; i <= n ; i ++ ) { //从第2个字符开始,边界情况很麻烦
for ( int j = 0 ; j < i ; j ++ ) { // 1个字符也算
if ( s [ j ] == s [ i - 1 ] && ( i - j < 3 || isPalindrome [ j + 1 ][ i - 2 ])) {
isPalindrome [ j ][ i - 1 ] = true ;
dp [ i ] = min ( dp [ j ] + 1 , dp [ i ]); //是j-1之前的,加上1
}
}
}
return dp [ n ];
}
Given a string S, find the longest palindromic substring in S.
You may assume that the maximum length of S is 1000, and there exists one unique longest palindromic substring.
传统方法:
string longestPalindrome ( string s ) {
//从中间往2边扩散的方法(比从2边向中间走强!走的地方都是有用的)
//并且回文串有2N-1(N N-1)个这样的中心(中心或者在任意一个字符或者在任意两个字符的之间)n^2
string ret ;
int length = s . size ();
if ( length <= 1 ) return s ;
// 1个或者2个字符,开始2边扩散! 扫一遍,所有的情况就都包括了
for ( int i = 0 ; i < length - 1 ; i ++ ) {
string res = longestPalindromeCore ( s , i , length );
if ( res . size () > ret . size ()) ret = res ;
}
return ret ;
}
string longestPalindromeCore ( const string & s , int mid , int length ) {
int i ;
//单点扩散
for ( i = 1 ; mid - i >= 0 && mid + i < length ; i ++ ) {
if ( s [ mid - i ] != s [ mid + i ]) break ;
}
//初始化为mid,从mid往2边搜索.注意这里的下标。最后一个参数是长度!!
string midstr1 ( s , mid - i + 1 , 2 * ( i - 1 ) + 1 );
if ( s [ mid ] != s [ mid + 1 ]) return midstr1 ;
//若该点和下个相同,则双点扩散
for ( i = 1 ; mid - i >= 0 && mid + 1 + i < length ; i ++ ) {
if ( s [ mid - i ] != s [ mid + 1 + i ])
break ;
}
string midstr2 ( s , mid - i + 1 , 2 * i );
return ( midstr1 . size () > midstr2 . size ()) ? midstr1 : midstr2 ;
}
DP方法:
string longestPalindrome ( string s ) {
//方法2: DP F(i,j) = F(i+1,j-1) if s[i] ==
// s[j].其中F(i,j)被定义为子串s[i...j]是否是回文
//所以枚举长度从1~N的子串(O(n2)),再判断是否为回文(O(1)).总体时间复杂度O(n2),
//空间复杂度O(n2)。
string ret ;
int length = s . size ();
if ( length <= 1 ) return s ;
bool isPalindrome [ 1000 ][ 1000 ] = { false };
int max_len = 1 , start = 0 ; // 最长回文子串的长度,起点
for ( int i = 0 ; i < length ; i ++ ) {
isPalindrome [ i ][ i ] = true ;
//[j][i]是否是回文 相当于下三角 或者j=i,j<length
for ( int j = 0 ; j < i ; j ++ ) {
//只有这样,j,i才是回文
if ( s [ j ] == s [ i ] && ( i - j < 2 || isPalindrome [ j + 1 ][ i - 1 ])) {
isPalindrome [ j ][ i ] = true ; //从小到大
if ( max_len < i - j + 1 ) {
max_len = i - j + 1 ;
start = j ;
}
}
}
}
return s . substr ( start , max_len );
}
另一种O(nlogn)的方法,使用后缀数组,将abcdcb镜面反转加上一个$符号,变为:abcdcb$bcdcba,于是将原问题转换为了求该字符串的所有后缀的最长公共前缀问题。实现较为复杂,可以参考 和这里
3. 给定字符串,最少插入多少字符,变成回文
state: f[i][j] 子串i~j变成回文,最少插入的字符数,
function: f[i][j] = if s[i] = s[j]: f[i+1][j-1] else: (1+min{f[i][j-1], f[i+1][j]})
intialize: f[i][i] = 0
answer: f[0][n-1]
for ( i = n - 1 ; i >= 0 ; -- i ) { // 倒着来,否则i+1没算出来
for ( j = i ; j < n ; ++ j ) {
if ( s [ i ] == s [ j ])
ans [ i ][ j ] = ans [ i + 1 ][ j - 1 ]; // 如果两个游标所指字符相同,向中间缩小范围
else
ans [ i ][ j ] = 1 + MIN ( ans [ i ][ j - 1 ], ans [ i + 1 ][ j ]); // 如果不同,典型的状态转换方程
}
}
printf ( "%d \n " , ans [ 0 ][ n - 1 ]);