版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
题目描述
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
样例
输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。
输入:输入: “cbbd”
"cbb 输出: “bb”
题解
方法一:暴力解法
- 首先遍历每一个字符在其前后添加特殊字符,是为了处理偶回文容易出错的情况;
- 从左至右遍历字符串的每一个字符,遍历到每一个字符的时候都看以该字符为中心,能够产生多长的回文子字符串
- 记录最长的回文子串的长度以及对应的下标
- 这种方法的缺点是每次遍历的过程都不会为下一次遍历提供帮助,时间复杂度O(N2)
- Mancher算法的优势是之前字符扩的过程可以指导后面字符扩的过程,使得每次的扩过程不是从无开始的,时间复杂度为O(N)
public String longestPalindrome(String s) {
if(s==null||s.length()==0||s.length()==1)
return s;
char c[]=new char[s.length()*2+1];
int j=0;
for(int i=0;i<s.length();i++) {
c[j]='#';
c[j+1]=s.charAt(i);
j+=2;
}
c[s.length()*2]='#';
int max=1,maxIndex=0;
for(int i=1;i<c.length;i++) {
int pre=i-1,after=i+1;
int len=1;
while(pre>=0&&after<c.length&&c[pre]==c[after]) {
len+=2;
pre--;
after++;
}
if(len>max) {
max=len;
maxIndex=i;
}
}
if(max==1)
return s.charAt(0)+"";
max=max/2;//真实max=4 max=3
int leftcount=max/2;//left=2 left=1
int leftIndex;
String str="";
if(c[maxIndex]=='#') {
leftIndex=(maxIndex-1)/2-leftcount+1;
str=s.substring(leftIndex, leftIndex+max);
}else {
leftIndex=maxIndex/2-leftcount;
str=s.substring(leftIndex, leftIndex+max);
}
return str;
}
方法二:Manacher算法
- 第一步都需要对每个字符开头和结尾加上特殊字符构造新一个新的字符串
- 定义一个整型数组count,记录每一个字符作为回文中心的情况下,扩出去得到的最大回文半径是多少。
- 定义变量right,表示在遍历过的字符中,根据其回文半径,记录最右即将到达的位置,初始设为-1。
- 比如在“#c#a#b#a#c#” 中,count[0]=1,表示目前回文半径向右只能扩到位置0.则right为1,表示最右即将到达的位置为1
- count[1]=2,表示表示目前回文半径向右2能扩到位置2,则right=3
- count[2]=1,表示表示目前回文半径向右2能扩到位置2,right不变
- 所以right就是遍历过的所有字符中向右扩出来的最大右边界,只要右边界更往右,right就更新
- 定义变量index,表示最近一次right更新时对应的回文中心的位置。
- 比如遍历到count[5]时,回文半径为6,所以right更新为11,之后的过程中,right没有再更新,所以index为5
- Mancher算法的加速过程在于记录每个位置的回文半径。
- 当计算第i个字符的回文半径时,首先看目前的right-1有没有包含i位置,
-
如果没有包含,那么和普通方法一样,依次遍历左右位置计算回文半径。
-
如果包含了i位置,比如right=11,在计算6~10时,right-1就包含了这些位置,那么他们的回文半径计算过程是可以加速计算的
-
index为中心,回文半径为right-1,i的回文半径有以下几种情况: -
-
即以i的对称位置i’作为回文中心的子串完全在index包含的内部,那么count[i]=count[i’]
-
第二种情况是i’的左边界在index左边界的外部,a代表左大位置的前一个字符,d是右大位置的后一个字符,左大’是以i’为中心左大的对称位置,右大’是以i为中心右大的对称位置,b是左大’位置的后一个字符,c是右大’位置的前一个字符,可以看出a==b并且b=c,c≠d,因为如果c=d,右大的位置更应该往外扩了,所以i的回文子串为右大~右大’
- 第三种情况左大和最小重合,此时i的右边界不能确定,有可能会扩的更大
-
- 当计算第i个字符的回文半径时,首先看目前的right-1有没有包含i位置,
public String longestPalindrome(String s) {
if(s==null||s.length()==0||s.length()==1)
return s;
char c[]=new char[s.length()*2+1];
int j=0;
for(int i=0;i<s.length();i++) {
c[j]='#';
c[j+1]=s.charAt(i);
j+=2;
}
c[j]='#';
int max=Integer.MIN_VALUE,maxIndex=0;
int right=-1,index=-1;
int count[]=new int[c.length];
for(int i=0;i!=c.length;i++) {
//右边界-1是否包含i
//2*index-i是i的对称点
//对于第二种情况时选择right-i
count[i]=right>i?Math.min(count[2*index-i], right-i):1;
while(i+count[i]<c.length&&i-count[i]>-1) {
if(c[i+count[i]]==c[i-count[i]])
count[i]++;//第三种情况
else
break;
}
//更新右边界和中心
if(i+count[i]>right) {
right=i+count[i];
index=i;
}
if(count[i]>max) {
max=count[i];
maxIndex=i;
}
}
String str="";
if(maxIndex%2==0) {
str=s.substring(maxIndex/2-(max-1)/2, maxIndex/2+(max-1)/2);
}else {
str=s.substring(maxIndex/2-(max-1)/2,maxIndex/2+(max-1)/2+1);
}
return str;
}