package main;
import java.util.Scanner;
public class Main {
/*
* 最长回文子串与最长回文子序列区别
* (1)给一个字符串,找出它的最长的回文子序列的长度。例如,如果给定的序列是“BBABCBCAB”,则输出应该是7,“BABCBAB”是在它的最长回文子序列。
* “BBBBB”和“BBCBB”也都是该字符串的回文子序列,但不是最长的。
* (2)注意和最长回文子串的区别(参考:最长回文串)!这里说的子序列,类似最长公共子序列LCS( Longest Common Subsequence)问题,可以是不连续的。
* 这就是LPS(Longest Palindromic Subsequence)问题。
*/
/***********************************求最长回文子串(方法一和方法二)*********************************************/
/*
* 方法一 动态规划法
* 比如P[i,j](表示以i开始以j结束的子串)是回文字符串,那么P[i+1,j-1]也是回文字符串
* P[i,j]=false 表示子串[i,j]不是回文串。P[i,j]=true 表示子串[i,j]是回文串。
* 初始化 P[i,i]=1;
* |- P[i+1, j-1], if(s[i]==s[j])
* P[i,j]=|
* |_ false, if(s[i]!=s[j])
*/
public static String longestPalindrome1(String s) {
int maxLen = 0; //记录最长回文串的长度
int start = 0; //记录最长回文串的开始位置
boolean[][] P = new boolean[1000][1000];
if(s.length()==1)
return s;
//初始化
for(int i = 0;i<s.length();i++){
P[i][i] = true; //P[i][j]表示以i开始,以j结束的子串
if(i<s.length()-1&&s.charAt(i)==s.charAt(i+1)){
P[i][i+1] = true;
start = i;
maxLen = 2;
}
}
for(int len = 3;len<=s.length();len++){ //长度从3开始
for(int i = 0;i<=s.length()-len;i++){ //设置初始位置
int j = i+len-1; //与i相对的位置
//若在两端的位置i,j位置处的字符相等,再者从i+1~j-1之间为true,更新
if(P[i+1][j-1]==true&&s.charAt(i)==s.charAt(j)){
P[i][j] = true;
maxLen = len;
start = i;
}
}
}
//System.out.println("start: "+start+" maxLen:"+maxLen);
if(maxLen>=2)
return s.substring(start, start+maxLen);
return null;
}
/*
* 方法二:中心扩展法
* 中心扩展就是把给定的字符串的每一个字母当做中心,向两边扩展,这样来找最长的子回文串。算法复杂度为O(N^2)
*
*/
public static String longestPalindrome2(String s) {
int maxLen = 0; //回文字串的最大长度
int start = 0; //回文字串的开始位置
if(s.length()==1)
return s;
//s的长度为奇数
for(int i = 0;i<s.length();i++){
int j = i-1; //i的左边
int k = i+1; //i的右边
while(j>=0&&k<s.length()&&s.charAt(j)==s.charAt(k)){
if(k-j+1>maxLen){
maxLen = k-j+1;
start = j;
} //if
j--;
k++;
} //while
}
//s的长度为偶数
for(int i = 0;i<s.length();i++){
int j = i; //i的左边
int k = i+1; //i的右边
while(j>=0&&k<s.length()&&s.charAt(j)==s.charAt(k)){
if(k-j+1>maxLen){
maxLen = k-j+1;
start = j;
} //if
j--;
k++;
} //while
}
return s.substring(start, start+maxLen);
}
/***********************************求最长回文子序列(方法一、方法二和方法三)*********************************************/
/* 方法一 动态规划:求最长回文子序列
* 对任意字符串,如果头和尾相同,那么它的最长回文子序列一定是去头去尾之后的部分的最长回文子序列加上头和尾。
* 如果头和尾不同,那么它的最长回文子序列是去头的部分的最长回文子序列和去尾的部分的最长回文子序列的较长的那一个。
* 设字符串为s,f(i,j)表示s[i..j]的最长回文子序列。
* 当i>j时,f(i,j)=0。
当i=j时,f(i,j)=1。
当i<j并且s[i]=s[j]时,f(i,j)=f(i+1,j-1)+2。
当i<j并且s[i]≠s[j]时,f(i,j)=max( f(i,j-1), f(i+1,j) )。
注意如果i+1=j并且s[i]=s[j]时,f(i,j)=f(i+1,j-1)+2=f(j,j-1)+2=2,这就是“当i>j时f(i,j)=0”的好处。
*/
public static int longestP1(String s) {
int[][] f = new int[1000][1000];
for(int i=s.length()-1;i>=0;i--){
f[i][i] = 1;
for(int j = i+1;j<s.length();j++){
if(s.charAt(i)==s.charAt(j)){
f[i][j] = f[i+1][j-1]+2;
}else{
f[i][j] = f[i][j-1]>f[i+1][j]?f[i][j-1]:f[i+1][j];
} //if
}
}
return f[0][s.length()-1];
}
//方法二,对方法一的改进,用不了那么多空间
//如果s.length()是奇数,则结果在第0行;如果是偶数,则结果在第1行。
public static int longestP2(String s) {
int[][] f = new int[2][1000];
int now = 0;
for(int i=s.length()-1;i>=0;i--){
f[now][i] = 1;
for(int j = i+1;j<s.length();j++){
if(s.charAt(i)==s.charAt(j)){
f[now][j] = f[1-now][j-1]+2;
}else{
f[now][j] = f[now][j-1]>f[1-now][j]?f[now][j-1]:f[1-now][j];
} //if
}
now = 1 - now;
}
if(s.length()%2==0)
return f[1][s.length()-1];
else
return f[0][s.length()-1];
}
//方法三,好理解
public static int longestP3(String s, int i, int j) {
if(i==j)
return 1;
if(i>j)
return 0;
if(s.charAt(i)==s.charAt(j))
return longestP3(s,i+1,j-1)+2;
else
return longestP3(s,i+1,j)>longestP3(s,i,j-1)?longestP3(s,i+1,j):longestP3(s,i,j-1);
}
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
while(scan.hasNext()){
String s = scan.next();
//求最长回文子串
System.out.println("方法一:动态规划:"+longestPalindrome1(s));
System.out.println("方法二:中心扩展法:"+longestPalindrome2(s));
//求最长回文子序列
System.out.println("方法一:动态规划-递归1:"+longestP1(s));
System.out.println("方法二:动态规划-递归2:"+longestP2(s));
System.out.println("方法三:动态规划-递归3:"+longestP3(s,0,s.length()-1));
}
}
}
最长回文子串 与 最长回文子序列
最新推荐文章于 2022-12-08 12:12:44 发布