给出一个字符串:cabadabae
我们不难看出其回文子串:aba,abadaba
这边简单介绍两种做法:
1.暴力:找出所有字串,然后依次判断是否为回文串,最后找出最长的回文子串
2.中心扩展:遍历字符串,把字符和字符间的空隙当作回文中心,然后向两边扩展来找到最长回文串
思考:
如果我们用中心扩展的算法,对于字符串:cabadabae,我们已经知道了以第3位b为中心的回文串aba和以第5位为中心的回文串abadaba了,那么对我们在判断后面以第6,7,8位为中心的回文串的时候有什么帮助吗?
情况1:对于以第6位a为中心的回文串,我们知道了2~4的字符串和6~7的字符串是一模一样的,我们还知道第3位b和第5位d不相同,所以不难得出以第6位a为中心的回文串的长度为1。
情况2:对于以第7位b为中心的回文串:我们知道了2~4的字符串和6~7的字符串是一模一样的,所以以第7位b为中心的回文串的长度至少为3,为什么是至少呢,因为对于第8位后面的字符我们不知道任何信息,还需要进行中心扩展,但是减少了扩展的次数。
情况3:对于以第8位a为中心的回文串:对于第8位后面的字符我们不知道任何信息,所以我们由前面得不到任何帮助,还需要进行中心扩展。
不难看出:我们利用了回文串的对称特性,用前面已知的回文串长度的信息来帮助后面去求回文串长度,就好像我们用前面的已知信息照亮了后面未知的一部分区域。所以整个算法的核心思路就出来了:通过对前面字符的中心扩展,我们将已知回文的右边界不断向后延申,在这个边界内我们都有可以利用的已知信息来帮助我们减少后半部分扩展的次数。
到这里我们需要的辅助变量就出来了:
1.p[i]:用这个数组来记录以第i位为中心的最长回文字串的半径
2.centre:已知的右边界最大的回文字串的中心
2.rm:已知字串的右边界(rm就等于centre+p[i],多加一个变量便于理解)
计算p[i]
接下来就不得不感叹算法的精妙简洁了
if(mr-i>p[j])
p[i]=p[j]
else
p[i]=mr-i;
//上面代码可以精简为p[i]=mr>i?min(p[2*centre-i],mr-i):1;
//j=2*centre-i
如果i+p[j] 还是在右边界里,那么p[i]=p[j],也就是情况1
如果i+p[j]在右边界外,那么p[i]的最小值为mr-i,也就是情况2
如果i就在右边界或者右边界右边,就是情况3,p[i]就先赋为1,之后再去匹配
处理原字符串
由于原字符串长度可能是偶数,导致回文中心为字符间的空隙,所以我们可以在两个字符间加入'#',这样字符串长度就是奇数了(n+n-1)
原字符串 | c | a | b | a | d | a | b | a | e | ||||||||
处理后的串 | c | # | a | # | b | # | a | # | d | # | a | # | b | # | a | # | e |
string s,str;
cin>>s;
str="@#";//可以加上一个开始和结束标记
for(i=0;i<s.size();i++)
{
str+=s[i];
str+='#';
}
核心代码
for(i=1;str[i]!='\0';i++)
{
p[i]=mr>1?min(p[2*id-i],mr-i):1;
while(str[i+p[i]]==str[i-p[i]])
p[i]++;//扩展
if(i+p[i]>mx)
{
mr=i+p[i];//右边界更新
centre=i;//最长回文串中心更新
}
}
我的评价是:妙极了!!!