涉及算法:dp+lcs
题目大意:对于给定的字符串S,求出最少需要在S中添加多少个字符可以使S对称(我们称此事S为回文串),即从左往右看s和从右往左看S是一样的,
题目分析:两种思路
思路一:设dp[i][j]为字符串S从第个字母到第j个字母S之间的这段字符串S(i,j)需要填加的字符数,使得S(i,j)为回文串。
题目大意:对于给定的字符串S,求出最少需要在S中添加多少个字符可以使S对称(我们称此事S为回文串),即从左往右看s和从右往左看S是一样的,
题目分析:两种思路
思路一:设dp[i][j]为字符串S从第个字母到第j个字母S之间的这段字符串S(i,j)需要填加的字符数,使得S(i,j)为回文串。
思路二:S中需要填加的字符数等于:S的长度-S和其逆序列S’的最长公共子序列的长度。用最长公共子序列的知识来求解
还有一个问题,因为S的长度可以高达500,那么势必需要一个5000x5000的二维数组,空间开销十分巨大,所以这里用了滚动数组来节省空间开销
代码如下:
import java.util.Scanner;
public class Main_1159 {
static int n;
static char[] a;
static short[][] dp;
public static void main(String[] args) {
Scanner in=new Scanner(System.in);
n=in.nextInt();
String s=in.next();
a=s.toCharArray();
dp4();
}
//思路一AC
static void dp(){
dp=new short[n][n];
for(int i=0;i<n;i++){
for(int j=i;j>=0;j--){
dp[i][j]=0;
}
}
//i要从右边开始j要从左边开始,这取决于转移方程:dp[i][j]=dp[i+1][j-1];
for(int i=n-2;i>=0;i--){
for(int j=i+1;j<n;j++){
if(a[i]==a[j]){
dp[i][j]=dp[i+1][j-1];
}else {
dp[i][j]=(short) Math.min(dp[i+1][j]+1, dp[i][j-1]+1);
}
}
}
System.out.println(dp[0][n-1]);
}
//思路一+滚动数组
//dp[i][j]=dp[i+1][j-1]改写成dp[i%2][j]=dp[(i+1)%2][j-1]
static int[][] dp2=new int[2][5001];
static void dp2(){
for(int i=n-1;i>=0;i--){
for(int j=0;j<n;j++){
if(i>=j){
dp2[i%2][j]=0;
}else {
if(a[i]==a[j]){
dp2[i%2][j]=dp2[(i+1)%2][j-1];
}else {
dp2[i%2][j]=Math.min(dp2[(i+1)%2][j]+1, dp2[i%2][j-1]+1);
}
}
}
}
System.out.println(dp2[0][n-1]);
}
<span style="white-space:pre"> </span>//思路二:
//用Lcs来解dp[i][j]:表示s[0,i-1]和s'[0,j-1]的最长公共子序列,s'为s的逆序列
static char[] b=new char[5000];//b为a的逆序列
static void dp4(){
for(int i=0;i<n;i++){
b[i]=a[n-i-1];
}
for(int i=0;i<=n;i++){
dp2[0][i]=0;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(a[i-1]==b[j-1]){
dp2[i%2][j]=(short) (dp2[(i-1)%2][j-1]+1);
}else {
dp2[i%2][j]=(short) Math.max(dp2[(i-1)%2][j], dp2[i%2][j-1]);
}
}
}
System.out.println(n-dp2[n%2][n]);
}
}