力扣第五题:寻找最长回文串
这个题的难点在于:
① 回文串中的字符可能有奇数个,也有可能有偶数个。若用一般方法,必须得分奇偶讨论。
eg: a b c d c b a
上述最长回文字符串为奇数个,对称轴为 d
a b b a
上述最长回文串为偶数个,对称轴为 b b
而只要将字符串做如下调整:
#a#b#c#d#a#b#c#
#a#b#b#a#
最长字符串的个数就成了奇数个,不需要分类讨论。
② 对字符串从左到右,以每一个字符为中心轴进行左右扩展,每一次扩展的起始半径都为1,造成了不必要的重复,时间复杂度较高。
解决办法:马拉车算法
优点:若是一般方法的左右拓展,RL的值必须从1开始拓展,但马拉车算法高效得求得了RL[i]数组,降低了时间复杂度。
RL[i]为以 i为轴扩展的回文串的半径
Maxright 为所能触及的回文串的最右
pos为所能触及最长回文串的对称轴
在解决这道题之前,我们首先要知道已经变成奇数个的字符串s1中,RL[i]-1 为以 I 为中心的回文串的长度。那么问题就变成了如何高效的求 RL[i] 数组。
在从左往右遍历 i 的过程中,要考虑 i 与 Maxright 的相对位置:
(1)i 在 Maxright 的左边,i 关于pos的对称位置为 j 。
第一种情况,j 很短,没有超过 Maxright 关于pos的对称位置,那么RL[i]就以RL[j] 为初始半径开始左右扩展,而RL[j]在遍历到 i 之前已经求过,所以RL[i]=RL[2*pos-1];
第二种情况,j很长,已经超过了Maxright关于pos的对称位置,我们所能看到的 RL[i] 的仅仅是 Maxright-i 的部分,所以RL[i] 以Maxright-1 为起始半径开始左右扩展。
(2)i 在Maxright的右边,说明 i 所在的位置还未触及,RL[i]的初始值为1。
if(i<MaxRight)
RL[i]=min(RL[2*pos-i],MaxRight-i);
else
RL[i]=1;
当超过左边界、超过有边界或者两边的字符不相等时,扩展结束。
while(i>=RL[i]&&i+RL[i]<len&&s1[i-RL[i]]==s1[i+RL[i]])
RL[i]+=1;
实时更新 MaxRight 和 pos 的值。
最后更新 Maxpos 和MaxRL的值。
class Solution {
public:
string longestPalindrome(string s) {
int len=s.length();
if(len<1)
return "";
string s1;
for(int i=0;i<len;i++)
{
s1+="#";
s1+=s[i];
}
s1+="#";//使成为奇数j字符串
len=s1.length();
int MaxRight=0;
int pos=0;
int MaxPos=0;
int MaxRL=0;
int *RL=new int [len];
memset(RL,0,len*sizeof(int));//初始化 RL数组
for( int i=0;i<len;i++)
{
if(i<MaxRight)
RL[i]=min(RL[2*pos-i],MaxRight-i);
else
RL[i]=1;
while(i>=RL[i]&&i+RL[i]<len&&s1[i-RL[i]]==s1[i+RL[i]])
RL[i]+=1;
if(i+RL[i]-1>MaxRight)
{
MaxRight=i+RL[i]-1;
pos=i;
}
if(RL[i]>=MaxRL)
{
MaxRL=RL[i];
MaxPos=i;
}
}
return s.substr((MaxPos-MaxRL+1)/2,MaxRL-1);/MaxPos-MaxRL+1)/2为最长回文串的起始位置,MaxRL-1为最长回文串的长度
}
};