在leetcode中做到最长回文子串,看了别人的解法,我自己也总结了一下(java实现)
暴力解决 时间复杂度O(n3)
思路:双层循环,每次判断【i,j】范围是否时回文,如果是回文,则比较保存的长度和当前长度。若当前的子串长,则更新起始坐标和长度。每次保存起始坐标和长度,最后做字符串的截取。
public static String longestPalindrome(String s) {
if(s.length()<1)
return "";
int pos=0,len=1;
for(int i=0;i<s.length();i++) {
for(int j=i+1;j<s.length();j++) {
if(Judge(s, i, j)&&len<j-i+1) {
pos=i;
len=j-i+1;
}
}
}
return s.substring(pos,pos+len);
}
public static boolean Judge(String s,int from,int to) {
while(from<=to) {
if(s.charAt(from)==s.charAt(to)) {
from++;
to--;
}
else {
return false;
}
}
return true;
}
动态规划 时间复杂度O(n2)
思路:在暴力解决的基础上做数据的存放,避免反复计算。bp[i][j]表示【i,j】是否为回文。
public static String longestPalindrome(String s) {
int len=s.length();
if(len==0)
return "";
boolean bp[][]=new boolean[len][len];
for(int i=0;i<len;i++) {
bp[i][i]=true;
}
int pos=0,length=1;
for(int i=0;i<len;i++) {
for(int j=0;j<i;j++) {
if(s.charAt(i)!=s.charAt(j)) {
bp[j][i]=false;
continue;
}
if(i-j<3) {
bp[j][i]=true;
}
else {
bp[j][i]=bp[j+1][i-1];
}
if(bp[j][i]&&i-j+1>length) {
pos=j;
length=i-j+1;
}
}
}
return s.substring(pos,pos+length);
}
中心扩散 时间复杂度O(n2)
思路:回文存在奇偶两种情况。所以在遍历的时候判断以当前i,或者i到i+1为中心向外扩散的最大长度。如若比原先保存的要大,则进行更新,最后返回截取的字符串
public static String longestPalindrome(String s) {
if(s.length()<2)
return s;
// 考虑到奇偶两种情况
int pos=0,len=1;
int one,two,max;
for(int i=0;i<s.length()-1;i++) {
one=expendAroundCenter(s, i, i);
two=expendAroundCenter(s, i, i+1);
max=Math.max(one, two);
if(max>len) {
pos=i-(max-1)/2;
len=max;
}
}
return s.substring(pos,pos+len);
}
// 获得以[from,to]扩展开的最大回文串
public static int expendAroundCenter(String s,int from,int to) {
while((from>=0&&to<s.length())&&s.charAt(from)==s.charAt(to)) {
from--;
to++;
}
return to-from-1;
}
manacher 算法 时间复杂度O(n)
思路:
- 首先,对字符串进行填充,每个字符间以及首尾都填充指定字符,使得填充后的字符的长度为奇数,这样就不用对回文进行奇偶判断了
- maxright为已存储的回文子串的最有边距,center为该状态下的回文中心
i 为当前遍历下标,mirror为i相对于center的镜像,数组f用来存储对应以i为中心的回文子串半径。 - 若maxright小于i 则直接依据中心扩散找到最长回文子串
- 若maxight大于i 此时center必定小于i,这时f[i]的值先取f[mirror]和maxright-i中的小的那个,在以f[i]为半径继续进行中心扩散,用以更新f[i]。理由如下:
第一种情况:maxright-i<f[mirror] 此时 f[i] 必定等于maxright-i
第二种情况:maxright-i=f[mirror] 此时 f[i]大于等于f[mirror] ,这种情况下需要以f[mirror]继续进行中心扩散
第三种情况:maxright-i>f[mirror] 此时 f[i]等于f[mirror]
想不通的自己画图想想。注意细节(f[mirror]为已知,即半径确定为f[mirror],超出半径不符合回文要求。此外maxright、center、maxleft(假设用,用于分析)这三个是由镜像关系的。)
最后合并情况就是先去maxright-i和f[mirror]中的小的那个,然后在以此为半径向外中心扩散尝试更新(尽可能使半径变大)。
public static String longestPalindrome(String s) {
if(s.length()<2)
return s;
int mirror=0,i,maxright=0,center=0;
int pos=0,length=1;//最大回文子串信息存储
int len=s.length();
int slen=2*len+1;
int f[]=new int[slen]; //存储结果
String adddivider=addDividers(s, '#');//插入分隔符后的字符串
int left,right;
for(i=0;i<slen;i++) {
// 从左到右依次处理的,一旦maxright大于遍历i,此时center 必定比i 要小
if(maxright>i) {
mirror=2*center-i;
f[i]=Math.min(f[mirror], maxright-i);
}
// 根据 镜像计算出来的的 小回文子串向两端继续扩散
// 此时回文扩散中心为i 当前满足的回文半径为f[i]
left=i-f[i]-1;
right=i+f[i]+1;
while((left>=0&&right<slen)&&adddivider.charAt(left)==adddivider.charAt(right)) {
f[i]++;//半径加一
left--;
right++;
}
// 更新maxright和center的值
if(i+f[i]>maxright) {
maxright=i+f[i];
center=i;
}
// 更新最大回文子串
if(f[i]>length) {
pos=(i-f[i])/2;
length=f[i];
}
}
return s.substring(pos,pos+length);
}
// 插入分隔符,使字符串 s的长度恒为奇数
public static String addDividers(String s,char divider) {
char []charArray=s.toCharArray();
int len=charArray.length;
StringBuilder build=new StringBuilder();
for(int i=0;i<len;i++) {
build.append(divider);
build.append(charArray[i]);
}
build.append(divider);
return build.toString();
}
测试入口
public static void main(String[] args) {
// TODO Auto-generated method stub
String s="cbbd";
// String s="aaaa";
// String s="abcda";
// String s="babad";
System.out.println(longestPalindrome(s));
}