hihocoder 1032 最长回文子串

来源:互联网 发布:淘宝折扣返利网 编辑:程序博客网 时间:2024/05/21 21:48

一般的求最长回文子串,我喜欢用两种方法。

当n不是很大时,我们可以用O(n^2)的方法, 即枚举中心点,然后向两边进行比较,当然长度为偶数的回文串是没有中心点的,所以我们得按照奇数和偶数两种进行求解。

char s[N];void findLongestPalindrome(char *s){//以枚举回文串的中心点来寻找回文串,时间复杂度(On^2),要分长度为奇偶两种情况int len = strlen(s);int maxlen = 0;int st;for(int i=0;i<len;i++){//回文串长度为奇数的时候int j = i-1;k=i+1;while(j>=0&&k<len&&s[j]==s[k]){if(k-j+1>maxlen){maxlen = k-j+1;st = j;}j--;k++;}}for(int i=0;i<len;i++){//回文串为偶数的时候int j = i;k = i+1;while(j>=0 && k<len && s[j] == s[k]){if(k-j+1>maxlen){maxlen = k-j+1;st = j;}j--;k++;}}//for(int i=st;i<st+maxlen;i++){//printf("%c",s[i]);//}printf("%d\n",maxlen);}
但是对于这道题目来说,n有10^6,所以n^2效率肯定是不行的,这个数量级的基本也就是用O(n)效率算法来解决了。

这种算法被称为manacher算法。

manacher算法能达到O(n)的原因是因为它定义了一个数组p[i],代表以第i个字符为中心,最长回文子串的半径为p[i],神奇的是,后面的p[i]可以利用前面已经算出的p[i]进行推导。

先给出Mancher算法的核心代码:

void findLongestPalindrome(char *s){//int len = strlen(s);int id=0,maxlen=0;k=0;ch[k++] = '*';for(int i=0;i<len;i++){ch[k++] = '#';ch[k++] = s[i];if(i==len-1)ch[k++] = '#';}ch[k++] = '\0';//printf("%s\n",ch);memset(p,0,sizeof p);for(int i=2;i<2*len+1;i++){if(p[id]+id>i)p[i] = Min(p[2*id-i],p[id]+id-i);elsep[i] = 1;while(ch[i-p[i]] == ch[i+p[i]])p[i]++;if(p[id]+id<i+p[i])id = i;if(maxlen<p[i])maxlen = p[i];}printf("%d\n",maxlen-1);}
刚才说了,算法是基于有个中心点的,所以偶数长度的无法处理,我们的做法是可以再每个字符中心加个没有出现过的符号,如'#',这样,所有的回文串的长度就都是奇数了。

p[id]刚才说了,是以第id个字符为中心的最长回文串的半径,那么如果现在循环到了第i个字符,那么如果在id<i中没有半径覆盖到i(即p[id]+id<=i),那么此时我们已知的最长回文子串的长度为1,剩下的就是向两边扩展比较了。

上面的是第一大情况,对于第二大情况,如果有p[id]+id>i,说明第i个字符之前已经在某个回文串之内了。

这种情况又分为3种情况。


这就是第一种情况,图中的i就是我们说的id,i+kj就是我们说的i,那么因为i+k是在以i为中心回文串种,那么与i+k对应的就是i-k那部分,如果i-k回文串有一部分在i的半径外,那么其实是不可能的,因为如果这样,那么i的半径就随之延伸了。所以此时p[i+k] = p[i]+i-k (对应到代码中就是p[i]=p[id]+id-i)。





这是第二种情况,就是i-k-[i-k]在以i为半径之内,说明我们现在遍历到的i+k最长的回文串长度也是在p[i]半径之内的,所以此时p[i+k] = p[i-k](在代码中就是p[id-(i-id))]=p[2*id-i];




第三种情况的p[i+k]值是和第二种情况是一样的,但是在这种情况下p[i+k]的值还可能增加,因为再往外延伸时,我们就不知道了,而第二种情况下p[i+k]=p[i-k],不可能再增加了。

上面的两大情况和3种小情况合起来就能得出以上的核心代码。



0 0