hihocoder 1032 最长回文子串

本文介绍了两种求解最长回文子串的方法:一种是枚举中心点的O(n^2)方法,适用于n较小的情况;另一种是Manacher算法,时间复杂度为O(n),适用于大数据量场景。通过插入特殊字符将所有回文串转换为奇数长度,并使用数组p记录以各字符为中心的最长回文半径。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

当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);
		else
			p[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种小情况合起来就能得出以上的核心代码。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值