根据对一些回文串的观察可以发现,回文串分为两类(依据回文串的对称中心)。
1,aa型 例如:aa,abba,abccba;
2, aba型 例如:aba,aabaa,abcba;
所以有了 解法一:根据回文串对称中心下手。
1,先遍历一次字符串,出现挨着相等的字符则将其下标存入一个数组a,出现间隔相等的字符则将其下标存入一个数组b。即筛选出子串,并用一个数组标记。
2,再遍历一次数组a,通过存入的下标对字符串进行左右探索,相同则长度+2,不同立刻跳出。记录最大值
3,再遍历一次数组b,通过存入的下标对字符串进行左右探索,相同则长度+2,不同立刻跳出。记录最大值
4,最长回文子串长度即为2,3获取到的最长回文串子度两者的更大值
#include
#include
int main()
{
char a[100]={0},b[100]={0},s[100]; //数组a存放aa类回文数的第二个a的下标,数组b存放aba类回文数的b的下标 ,数组s为输入的字符串
int max1=1,max2=1,t1=2,t2=3; //max1为最大的aa类回文数长度,max2为最大的aba类回文数长度 //t是否个具体的回文串的长度
printf("输入一个字符串:");
scanf("%s",s);
int l=strlen(s);
for(int i=0,j=0,k=0;i<l-1;i++)
{
if(s[i]==s[i+1]) a[j++]=i+1; //查找aa类回文数,//存取第二个a的下标
if(s[i]==s[i+2]) b[k++]=i+1; //查找aba类回文数,//存取b的下标
}
if(a[0]!=0) //获取aa型最长回文子串的长度,若a[0]!=0,说明存在aa型,长度至少为2
for(int i=0;a[i]!=0;i++,max1=max1>t1?max1:t1,t1=2) //遍历a数组获取下标,更新最大值,重置回文串长度t=2
{
for(int j=1;a[i]-1-j>=0&&a[i]+j<=l;j++)
{
if(s[a[i]-1-j]==s[a[i]+j]) t1+=2; //仍然对称,该回文子串长度+2
else break; //一旦失败,立即退出当前循环
}
}
if(b[0]!=0) //获取aba型最长回文子串的长度,若b[0]!=0,说明存在aba型,长度至少为3
for(int i=0;b[i]!=0;i++,max2=max2>t2?max2:t2,t2=3) //遍历b数组获取下标,更新最大值,重置回文串长度t=3
{
for(int j=1;b[i]-1-j>=0&&b[i]+1+j<=l;j++)
{
if(s[b[i]-1-j]==s[b[i]+1+j]) t2+=2; //仍然对称,该回文子串长度+2
else break; //一旦失败,立即退出当前循环
}
}
printf("输入的字符串中最长回文子串长度为:%d\n",max1>max2?max1:max2); //输出两者的更大值
return 0;
}
//BY 陌生兔
第一种方法讲到,回文串分为aa型,aba型,导致需要分情况讨论,给算法带来不便。所以有了 解法二,避开回文类型。
1,马拉车算法 创造性的用一个符号(如#)插入到字符串之间形成一个新串,强制使得新串中回文串都变成aba型。例如:aa处理后变为#a#a#,aba处理后变为#a#b#a#。
这仅是马拉车的第一个神奇之处,当然这个方法也可以用到第一个解法里去。
2,马拉车算法,定义了一个回文半径函数,
例如:#a#a#半径为3,#a#b#a#半径为4。
这是马拉车算法最核心的地方,减少了许多不必要的操作。使得时间复杂度趋向于线性O(n)。
3,回文半径的作用,每个回文串都有一个回文半径,由于回文串的特性,顺逆读取相同,或者说关于某一点对称。由于大家对数学了解很多,因此我用数学概念来类比,方便大家理解。
回文串=偶函数----------------------------dabac的aba---------------------变量s
回文串对称点--------原点------------------aba中的b----------------------变量 centre
回文串边界-----------定义域边界-------------dabac的d和c----------------变量right_border(由于是从左往右扫描,左边界用不着)
回文串半径-----------定义域边界到原点的距离--------dabac中aba的半径为2---------变量radius
4,
利用回文串的特点(对称性/正逆相同性),减少一些不必要的操作。
接下来是核心中的核心了,很多人并未完全了解马拉车算法,只知道个大概,原因就是不知道该句算法的内核。
if(i>=right_border)
radius[i]=1;
else
radius[i]=radius[2*centre-i]<right_border-i?radius[2*centre-i]:right_border-i;
我来解释一下:该代码的意图是
(1)如果i超出了上一回文串的边界,那么该回文串i的半径赋初值为1.
(2)如果i仍处于上一回文串的边界内,那么radius[i]取radius[2centre-i]和right_border-i中的小值。
其中radius[2centre-i]是i关于centre的对称点的值,其中right_border-i是i与右边界的距离。按照常识,因为对称性,radius[i]=radius[2centre-i],但是因为我们所有的操作都是在一个普通字符串中,输入的字符串本身绝大可能不是回文串,因此对称性不能完全存在。例如:a badab e,当centre指向d时,即centre=3,显然right_border指向e,即right_border=6。i++后,当i指向第二个b时,即i=5,此时第一个b的半径为2,而第二个b的半径显然为1,≠2.这是为什么呢,因为第一个b左边的那个a并不是以d为centre的回文串 中的字符。所以我们应该考虑只由定义域内的回文半径,即right_border-i,对称的那个点的半径在定义域内的最多最多是right_border-i,所以要取其中的小值。
确实不太好理解,再简述一遍,
若2centre-i处的定义域全在centre的定义域内,那么radius[i]=radius[2centre-i];
若2centre-i处的定义域超出了centre的定义域,那么其定义域在centre的定义域的范围为2centre-i到左边界的值,即right_border-i。
由于是从左往右扫描,2centre-i的半径早已经存入了,所以将radius[2*centre-i]的有效半径作为radius[i]的起始值,然后再执行后续的代码,该值是否增加由后面的判断决定。由于初始值不是从0开始,所以减少了大量不必要的操作。使得while循环接近线性复杂度O(n)。
#include
#include
int main()
{
int ls,max=0;
char str[100],s[200],radius[200];
printf("输入一个字符串:");
scanf("%s",str);
ls=strlen(str);
s[0]='*';s[1]='#';
for(int i=0;i<ls;i++)
s[i*2+2]=str[i],s[i*2+3]='#';
ls=ls*2+2;
int centre=0,right_border=0;
for(int i=1;i<ls;i++)
{
if(i>=right_border) radius[i]=1;
else radius[i]=radius[2*centre-i]<right_border-i?radius[2*centre-i]:right_border-i;
while(s[i+radius[i]]==s[i-radius[i]]) radius[i]++;
if(radius[i]+i>right_border) right_border=radius[i]+i,centre=i;
max=max>radius[i]?max:radius[i];
}
printf("最长回文串长度为%d\n",max-1);
return 0;
}
//BY陌生兔
纯手打原创,转载请注明出处。