题意:给定一个字符串,至少插入几个字符才能使它变成回文串。
题解:总结了三种方法,具体如下。
方法一:
s1 为原字符串, s2 由 s1 从右至左复制得到。
dp [ i ] [ j ] 表示让 s1 [ i ] 与 s2 [ j ] 左边的字符串完全相等最少需要插入的字符个数。
1.当 s1 [ i ] != s2 [ j ] 时,
假设已知:
substr1 : ·······s1 [ i - 1 ]
substr2 : ·······s2 [ j ]
substr1 == substr2
现在要求 dp [ i ] [ j ], 那么必须在 s2 [ j ] 的右边添加一个等于 s1 [ i ] 的字符, 即
substr1 : ·······s1 [i-1], s1 [ i ]
substr2 : ·······s2 [ j ], s1’[ i ]
那么 dp [ i ] [ j ] = dp [ i - 1] [ j ] + 1
同理 : dp [ i ] [ j ] = dp [ i ] [ j - 1] + 1;
那么 dp [ i ] [ j ] = min ( dp [ i - 1 ] [ j ] + 1, dp [ i ] [ j - 1 ] + 1 );
2.当 s1 [ i ] == s2 [ j ] 时,
假设已知:
substr1 : ········s1 [ i - 1 ]
substr2 : ········s2 [ j - 1 ]
substr1 == substr2
dp [ i ] [ j ] = dp [ i - 1 ] [ j - 1 ]
注意:
1:由于s2 是由 s1 从右至左复制得到的, 根据对称性。答案应该为 dp [ len ] [ len ] / 2. ( 我试过只求到 dp [ len / 2 ] [ len / 2 ],但是某些细节没考虑到,老是 wrong, 放弃了。有知道的麻烦回复下。)
2.初始化要注意,dp [ i ] [ 0 ] = dp [ 0 ] [ i ] = i。道理很简单,假如要是 s1 的第 i 个字符与 s2 的第 0 个字符的左边完全一样,需要在 s2 的左边添加 s1[1], s1[2] ··· s1 [ i ].
#include <iostream> using namespace std; #define N 5005 short dp[N][N]; char s1[N], s2[N]; int min ( int a, int b ) { return a < b ? a : b; } int main() { int len, i, j; while ( scanf("%d",&len) != EOF ) { scanf("%s",s1+1); i = 1; j = len; while ( i <= len && j >= 1 ) { s2[i] = s1[j]; i++; j--; } for ( i = 0; i <= len; i++ ) dp[i][0] = dp[0][i] = i; for ( i = 1; i <= len; i++ ) { for ( j = 1; j <= len; j++ ) { if ( s1[i] == s2[j] ) dp[i][j] = dp[i-1][j-1]; else dp[i][j] = min ( dp[i-1][j] + 1, dp[i][j-1] + 1 ); } } printf("%d\n", dp[len][len] / 2 ); } return 0; }
#include <iostream> using namespace std; #define N 5005 short dp[N][N]; char s1[N], s2[N]; int max ( int a, int b ) { return a > b ? a : b; } int main() { int len, i, j; //freopen("a.txt","r",stdin); while ( scanf("%d",&len) != EOF ) { scanf("%s",s1+1); i = 1; j = len; while ( i <= len && j >= 1 ) { s2[i] = s1[j]; i++; j--; } for ( i = 1; i <= len; i++ ) dp[i][0] = dp[0][i] = 0; for ( i = 1; i <= len; i++ ) { for ( j = 1; j <= len; j++ ) { if ( s1[i] == s2[j] ) dp[i][j] = dp[i-1][j-1] + 1; else dp[i][j] = max ( dp[i-1][j], dp[i][j-1] ); } } if ( dp[len][len] == 0 ) dp[len][len] = 1; printf("%d\n", len - dp[len][len] ); } return 0; }
方法三:
设s[1]..s[n]表示字符串1至n位,i为左游标,j为右游标 ,则 i 从 len 递减,j 从 i+1 开始递增。这样做的效果是,开始令最右边的部分变为回文串,而后回文串逐渐向左边大。
dp[i][j]表示i和j之间至少需要插入多少个字符才能构成一个局部的回文串, 初始置全0 ,我们最终需要得到的值是dp[1][n]。则:
if ( s [ i ] == s [ j ] ) //如果两个游标所指字符相同,向中间缩小范围
dp [ i ] [ j ] = dp [ i + 1 ] [ j - 1 ];
else
dp [ i ] [ j ] = 1 + min( dp [ i + 1 ] [ j ] , dp [ i ] [ j - 1 ])
下面的代码用滚动数组优化了内存。
#include <iostream> using namespace std; #define N 5005 short dp[2][N]; char s[N]; int min ( int a, int b ) { return a < b ? a : b; } int main() { int len,i,j; //freopen("a.txt","r",stdin); while ( scanf("%d",&len) != EOF ) { scanf("%s",s+1); memset(dp,0,sizeof(dp)); for ( i = len; i >= 1; i-- ) { for ( j = i+1; j <= len; j++ ) { if ( s[i] == s[j] ) dp[i%2][j] = dp[(i+1)%2][j-1]; else dp[i%2][j] = min ( dp[i%2][j-1], dp[(i+1)%2][j] ) + 1; } } printf ( "%d\n",dp[1][len]); } return 0; }