朴素算法:
思想:根据回文的定义,枚举所有长度严格大于等于2的子串,以此判断是否回文。
优化:只针对大于“当前的到最长回文子串长度”的子串进行回文验证。
当得到一个更长的回文子串,只记录开始位置start及长度maxLen;
直接上代码:
public static String longestPalindrome(String s){
int len=s.length();
if(len<2){
return s;
}
int maxLen=1;
int start=0;
char[] charArray=s.toCharArray();
//之所以转成charArray是因为s.charAt(i)每次使用是会判断越界
for(int i=0;i<len-1;i++){
for(int j=i+1;j<len;j++){
if(j-i+1>maxLen&&check(charArray,i,j)){
//check()用于判断s[i...j]是否回文。
maxLen=j-i+1;
start=i;
}
}
}
return s.substring(start,start+maxLen);
}
public static boolean check(char[] charArray,int l,int r){
while(l<r){
if(charArray[l]!=charArray[r]){
return false;
}
l++;
r--;
}
return true;
}
时间复杂度: O(N3)
空间复杂度:O(N)
N为字符串长度。
动态规划:
回文本身就具有状态转移的性质,一个长度大于2的回文串去掉两端各一个字符后仍然是回文串。求最长回文子串的动态规划算法就是利用了这一性质。
第一步:定义状态
dp[i][j]表示:s[i...j]是否是回文串。s[i...j]两端均为闭区间。
第二步:找状态转移方程
dp[i][j]= (s[i]==s[j])&&dp[i+1][[j-1];
说明:
· 为什么这样找是因为 s[i...j] 是否是回文串是由它去掉两端各一个字符后子串的状态和两端字符是否相等转移得来的。
动态规划算法也可以理解为表格法,看下下面这张表格应该会清晰点:
由于我们规定 i <= j,那么我们只需要看表格对角线上半部分即可。
因为s[i]==s[i],对角线dp[i][i]始终是true。
· 出现dp[i+1][j-1]就需要考虑特殊情况:当去掉s[i...j]头尾两个字符得到子串s[i+1...j-1]的长度严格小于2时,
即 j-1-(i+1)+1<2时,整理得 j-i<3时,此时s[i...j]是否回文只取决于s[i]是否等于s[j]。即当子串s[i...j]的长度等于2或3的时候,s[i...j]是否回文支取决与两端是否相等。
第三步:初始化
单个字符一定是回文串,因此把对角线初始化为true。
另外由第二步,当s[i...j]的长度为2时,只要判断s[i]==s[j],这就导致。。对角线的数值不会被参考。所以上一句话无卵用。
第四步:输出
为了输出方便,我们只记录更新maxLen时的子串的开始位置start以及长度maxLen 就可以用subString()方法得到子串。
上代码:
public static String longestPalindrome(String s){
int len=s.length();
if(len<2){
return s;
}
int maxLen=1;
int start=0;
boolean[][] dp=new boolean[len][len];
char[] charArray=s.toCharArray();
//之所以转成charArray是因为s.charAt(i)每次使用是会判断越界
for(int i=0;i<len;i++)
dp[i][i]=true;
for(int j=1;j<len;j++){
for(int i=0;i<j;i++){
if(charArray[i]!=charArray[j]){
dp[i][j]=false;
}else {
if(j - i < 3){
dp[i][j]=true;
}else {
dp[i][j] = dp[i+1][j-1];
}
}
//更新回文最大长度
if(dp[i][j]&&j-i+1>maxLen){
maxLen=j-i+1;
start=i;
}
}
}
return s.substring(start,start+maxLen);
}
时间复杂度:O(N2),N为字符串长度。
空间复杂度:O(N2),使用了一张二维表来记录。
中心扩散法:
所谓中心扩散算法,就是Manacher,俗称“马拉车”。
我们遍历整个字符串,以每一个字符为中心让它向两端扩散,利用回文串堆成的特点,找出最长扩散的距离,这个最长距离也就是“回文半径”。
遍历每一个字符让它作为中心位置时间复杂度为 O(N),再从中心位置往两端扩散的时间复杂度为O(N),所以时间复杂度可以降到O(N2).
但是,问题来了,怎么找中心位置?
我们有两种选择:
一、当总回文长度为奇数个时,即这个中心字符就是该回文串的最中心,它对是否回文结果没有影响。
二、当总回文长度为偶数个时,即以这个字符和它的下一个字符为中心,实际上的最中心是这两个字符的间隔中。
对于每种情况,我们用两个分别得到其最大回文半径,然后选择得到较长者。
public static String longestPalindrome(String s){
int len=s.length();
if(len<2)return s;
int res[]=new int[2];
int maxLen=0;
//由于每种情况我们想得到两个值,即起始点和回文半径,因此用长度为2的数组
//[1]位置存储长度,[0]位置存储起始点
int[] odd=Spread(s,i,i);
int[] even=Spread(s,i,i+1);//i<s.length()-1
int max[]=odd[1]>even[1]?odd:even;
if(max[1]>maxLen){
res=max;
maxLen=max[1];
}
return s.subString(res[0],res[0]+res[1]);
}
关于Spread()函数,其实和朴素算法中的类似。
public static int[] Spread(String s,int l,int r){
int len=s.length();
while(l>=0&&r<len){
if(s.charAt(l)==s.charAt(r)){
l--;
r++;
} else {
break;
}
}
return new int[]{l+1,r-l-1};
}