求最长回文子串读书笔记

原创作品,出自 “晓风残月xj” 博客,欢迎转载,转载时请务必注明出处(http://blog.csdn.net/xiaofengcanyuexj)。

由于各种原因,可能存在诸多不足,欢迎斧正!

何为回文串?

       众所周知,回文串就是左右对称的字符串。

 给定一个字符串,要求其中的最长回文子串。这是一个比较简单基本的问题。

1.暴力枚举。

       枚举以每个字符为中心,看左右延伸的最长回文串,这里注意分奇偶,总的时间复杂度为O(N^N) .这是最简单最基本的算法,通常是不可取的。

#include"iostream"
#include"cstdio"
#include"cstring"
using namespace std;

const int MAXN=1000000+100;
char str[MAXN*2];//待处理字符串

int Solve()
{
	int i,l,tmp,ans=-1,len=strlen(str);
	for(i=0;str[i];i++)
	{
		l=0;tmp=1;
		while(i-l-1>=0&&i+l+1<len)//奇数时
		{
			if(str[i-l-1]==str[i+l+1])
				l++,tmp+=2;
			else break;
		}
		if(ans<tmp)
			ans=tmp;

		l=0;tmp=0;
		while(i-l>=0&&i+l+1<len)//偶数时,只算左边;右边的可以是下一个的左边
		{
			if(str[i-l]==str[i+l+1])
				l++,tmp+=2;
			else break;
		}
		if(ans<tmp)
			ans=tmp;
	}
	return ans;
}

int main()
{
	int tag=1;
	while(~scanf("%s",str))
	{
		if(strcmp(str,"END")==0)break;	
		printf("Case %d: %d\n",tag++,Solve());
	}
	return 0;
}


 

2.后缀数组。

      将原字符串翻过来接在新字符串的后面,中间用某一个特殊的字符串隔开,这样问题就变成了求新字符串分属两部分的后缀的最长前缀。时间复杂度为O(N*log(N)),个人觉得一般问题是可以解决的。

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;


//*****************************************************************
const int MAXN=(1000000+100)*2;
char str[MAXN];//待处理字符串
int sa[MAXN];//求得的后缀数组
int wa[MAXN],wb[MAXN],wv[MAXN],wh[MAXN];
int cmp(int *r,int a,int b,int l)
{
	return r[a]==r[b]&&r[a+l]==r[b+l];
}
//求后缀数组sa[],下标1到n-1(此处n=strlen(str)+1)有效后缀
//将str的n个后缀从小到大进行排序之后把排好序的后缀的开头位置顺次放入sa中。
//保证Suffix(sa[i])<Suffix(sa[i+1])
//1<=i<n,sa[0]存放人为添加在末尾的那个最小的后缀
//倍增算法的时间复杂度为O(nlogn)
//倍增算法的空间复杂度都是O(n)
void da(char *r,int *sa,int n,int m)
{
	int i,j,p,*x=wa,*y=wb,*t;
	for(i=0;i<m;i++) wh[i]=0;
	for(i=0;i<n;i++) wh[x[i]=r[i]]++;
	for(i=1;i<m;i++) wh[i]+=wh[i-1];
	for(i=n-1;i>=0;i--) sa[--wh[x[i]]]=i;
	for(j=1,p=1;p<n;j*=2,m=p)
	{
		for(p=0,i=n-j;i<n;i++) y[p++]=i;
		for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
		for(i=0;i<n;i++) wv[i]=x[y[i]];
		for(i=0;i<m;i++) wh[i]=0;
		for(i=0;i<n;i++) wh[wv[i]]++;
		for(i=1;i<m;i++) wh[i]+=wh[i-1];
		for(i=n-1;i>=0;i--) sa[--wh[wv[i]]]=y[i];
		for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
			x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
	}
	return;
}

int rank[MAXN],height[MAXN];
//定义height[i]=suffix(sa[i-1])和suffix(sa[i])的最长公
//共前缀,也就是排名相邻的两个后缀的最长公共前缀
//任意两个起始位置为i,j(假设rank[i]<rank[j])的后缀的最长公共前缀
//为height[rank[i]+1]、height[rank[i]+2]…height[rank[j]]的最小值
void calheight(char *r,int *sa,int n)
{
	int i,j,k=0;
	for(i=1;i<=n;i++) rank[sa[i]]=i;
	for(i=0;i<n;height[rank[i++]]=k)
		for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
		return;
}

int main()
{
	int len,i,ans,tag=1;
	while(~scanf("%s",str))
	{
		if(strcmp(str,"END")==0)break; 
		len=strlen(str);
		str[len]='#';
		for(i=0;i<len;i++)
			str[len+1+i]=str[len-i-1];
		str[len+len+1]=0;
		da(str,sa,len*2+2,250);
		calheight(str,sa,len*2+1);
		
		ans=1;
		for(i=1;i<=len*2+1;i++)
		{
			if((sa[i]-len)*(sa[i-1]-len)<0&&height[i]>ans&&sa[i]+sa[i-1]+height[i]==len*2+1)
				ans=height[i];
		}

		//printf("str=%s\n",str);
		printf("Case %d: %d\n",tag++,ans);
	}
	return 0;
}


 

3.Manacher算法。

        这是我这篇文章要注重介绍的算法,这是个求回文串的高效的算法,时间复杂度有效的降到O(N),这是普遍可以接受的。

Manacher算法主要思想:

      1.在原字符的间隙间插入一个未曾出现的字符,从而将字符串的长度变为奇数。如abab变化之后为#a#b#a#b#。

      2.开辟一个数组num[i],记录以该位置中心的回文串向右延伸的长度(包括本身),则以该字符为中心的回文串的长度为num[i]-1.如#a#b#a#b#,对应的num为1,2,1,4,1,4,1,2,1。

      3.转移规则,也就是该算法的核心。若当前要求num[i],假设之前已经求出的延伸至最右端的位置为MxR,则对应的回文串的中心位置为pos;设j为i关于pos的对称位置,则有若MxR>i,num[i]>=Min(num[j],MxR-i),否则num[i]=1(其中j=2*pos-i);之后再循环比较str[i-num[i]]与str[i+num[i]],若相等,num[i++](说明回文串可以向左右两边延伸);否则跳出。为什么会这样呢?若MxR>i,则说明i是以pos为中心位置的回文串的一部分;又j、i关于pos对称,由回文串的左右对称的属性,num[i]>=Min(num[j],MxR-i);之后就是延伸,找出num[i]了。从而保证在线性时间复杂度O(N)内求出以每个为中心的最长回文串了。具体的参考一下转载大神的图:(此图中mx=MxR,id=pos)

                                   

#include"iostream"
#include"cstdio"
#include"cstring"
using namespace std;

const int MAXN=1000000+100;
char str1[MAXN*2],str2[MAXN*2];//待处理字符串
int num[MAXN*2];

//将str1变成str2,如abab变成$#a#b#a#b#
void init()
{
	int i,id;
	str2[0]='$';
	str2[1]='#';
	for(i=0,id=2;str1[i];i++,id+=2)
	{
		str2[id]=str1[i];
		str2[id+1]='#';
	}
	str2[id]=0;
}

//Manacher算法求最长回文子串,时间复杂度为O(N)
int Manacher()
{
	int i,ans=0,MxR=0,pos;
	for(i=1;str2[i];i++)
	{
		if(MxR>i)num[i]=num[pos*2-i]<(MxR-i)?num[pos*2-i]:(MxR-i);
		else num[i]=1;
		while(str2[i+num[i]]==str2[i-num[i]])
			num[i]++;
		if(num[i]+i>MxR)
		{
			MxR=num[i]+i;
			pos=i;
		}
		if(ans<num[i])
			ans=num[i];
	}
	return ans-1;
}

int main()
{
	int ans,tag=1;
	while(~scanf("%s",str1))
	{
		if(strcmp(str1,"END")==0)break;
		init();
		ans=Manacher();
		printf("Case %d: %d\n",tag++,ans);
	}
	return 0;
}

具体应用见http://blog.csdn.net/xj2419174554/article/details/10295755

                      http://blog.csdn.net/xj2419174554/article/details/10295917

从上所述,求最长回文子串大概有这三种方法,可能在暴力求的过程中能用二分法加速。

以上如有错误的地方,请斧正。此文参考http://www.cnblogs.com/wuyiqi/archive/2012/06/25/2561063.html在此表示感谢!


 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: 最长回文子串可以通过两种方法来实现。第一种是使用中心扩展法,代码如下: ```python class Solution: def check(self, s, l, r): while l >= 0 and r < len(s) and s[l == s[r]: l -= 1 r += 1 return l + 1, r - 1 def longestPalindrome(self, s: str) -> str: start, end = 0, 0 for x in range(len(s)): l1, r1 = self.check(s, x, x) l2, r2 = self.check(s, x, x + 1) if r1 - l1 > end - start: start, end = l1, r1 if r2 - l2 > end - start: start, end = l2, r2 return s[start:end+1] ``` 第二种方法是使用动态规划,代码如下: ```python class Solution: def longestPalindrome(self, s: str) -> str: res = '' for i in range(len(s)): start = max(0, i - len(res) - 1) temp = s[start:i+1] if temp == temp[::-1]: res = temp else: temp = temp<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [5. 最长回文子串(Python 实现)](https://blog.csdn.net/d_l_w_d_l_w/article/details/118861851)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [LeetCode(Python3)5.最长回文子串](https://blog.csdn.net/weixin_52593484/article/details/124718655)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [力扣 (LeetCode)刷题笔记5.最长回文子串 python](https://blog.csdn.net/qq_44672855/article/details/115339324)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值