题目分析:查找一个字符串中中最长回文字符串
1.算法1:暴力检索
既然要查最长的回文字符串,那么直接对每一个字符,以它为中心,找到最长的回文字符串,更新最大值最小值就行了。注意要分两种情况,1
/**对每一个字母,都查找最大的回文串的长度,left,right分别表示回文串的起始位置*/
public String longestPalindrome(String s) {
int start = 0, end = 0;
int maxLen = 1;
for (int i = 0; i < s.length(); i++) {
int left = i;
int right = i;
while (left > -1 && right < s.length()
&& s.charAt(left) == s.charAt(right)) {
left--;
right++;
}
if (maxLen < right - left + 1) {
maxLen = right - left + 1;
start = left + 1;
end = right - 1;
}
/**字符串长度为偶数*/
left = i;
right = i + 1;
while (left > -1 && right < s.length()
&& s.charAt(left) == s.charAt(right)) {
left--;
right++;
}
if (maxLen < right - left + 1) {
maxLen = right - left + 1;
start = left + 1;
end = right - 1;
}
}
return s.substring(start, end + 1);
}
2. 动态规划
感觉第一个方法代码太多,而且很多重复计算,能不能之后的结果依据之前的结果,就想到了使用动态规划,主要是长度是慢慢扩展的,可以向依据长度变化的动态规划。
/**动态规划的思路,主要是考虑其实规划的时候是从内向外的扩展,主要考虑回文串的长度*/
public String longestPalindrome(String s) {
int start=0;
int end=0;
int max=1;
int len=s.length();
if(len<1) return "";
if(len==1) return s;
char[] a=s.toCharArray();
boolean[][] dp=new boolean[len][len];
/**长度为1和2*/
for(int i=0;i<len;i++)
{
dp[i][i]=true;
if(i<len-1&&a[i]==a[i+1])
{
dp[i][i+1]=true;
if(max<2)
{
max=2;
start=i;
end=i+1;
}
}
}
/**这样设计循环的理由,其实我们规划的条件是len比较小的先算,再算len比较大的
* i为起始位置
* j为结束位置*/
for(int l=3;l<=len;l++)
{
for(int i=0;i+l<=len;i++)
{
int j=i+l-1;
if(a[i]==a[j])
{
dp[i][j]=dp[i+1][j-1];
if(dp[i][j]&&l>max)
{
max=l;
start=i;
end=j;
}
}
}
}
return s.substring(start, end+1);
}
3. manacher算法
之上的两个算法都是o(n^2).然后搜索了一下,就发现被安利的manacher算法。先解释一下算法原理吧。算法主要是依据回文字符串的对称性来减少计算。
首先,为了消除诸如第一个算法中出现的奇数,偶数的影响,我们先进行一下预处理,在每一个字母之前都加一个#,这样每一个都为中心的都是回文串都是奇数个。为了防止处理中出现越界情况,所以我们可以在字符串的开始和结束加入特殊字符,比如(^$);
其次,为了更好的利用回文字符串的对称性,我们只需要对称字符串能够到达的最远位置r,以及对称中心cen.最后我们分情况讨论
假设对称中心为i,现在需要计算i+k位置,其对应位置为(i-k),P[i]为i向右延伸的回文串的长度
(1)i+k 大于r,那么之前的计算跟现在的没关系,只能仿照方法1计算
(2)i+k小于r 但是i+k+P[i-k]<r 根据对称性 P[i+k]=P[i-k];
(3)i+k小于r,i+k+P[i-k]>=r P[i+k]=r-(i+k)
源代码如下:
public String longestPalindrome(String s) {
String str=prepare(s);
int n=str.length();
int cen=0;
int r=0;
int[] p=new int[n];
char[] a=str.toCharArray();
for(int i=1;i<n-1;i++)
{
int j=2*cen-i;
p[i]=i<r?Math.min(r-i, p[j]):0;
while(a[i+p[i]+1]==a[i-p[i]-1])
p[i]++;
if(p[i]+i>r)
{
cen=i;
r=p[i]+i;
}
}
/**找到长度最大的元素*/
int len = 0;
int centerIndex = 0;
for (int i = 1; i < n-1; ++i){
if(p[i] > len){
len = p[i];
centerIndex = i;
}
}
return s.substring((centerIndex-len-1)/2, (centerIndex+len-1)/2);
}
public String prepare(String s)
{
if(s.length()==0) return "^$";
String ret="^";
for(char c:s.toCharArray())
{
ret+="#"+c;
}
ret+="#$";
return ret;
}