题目1 :最长回文子串
时间限制:1000ms
单点时限:1000ms
内存限制:64MB
描述
小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相帮助,在编程的学习道路上一同前进。
这一天,他们遇到了一连串的字符串,于是小Hi就向小Ho提出了那个经典的问题:“小Ho,你能不能分别在这些字符串中找到它们每一个的最长回文子串呢?”
小Ho奇怪的问道:“什么叫做最长回文子串呢?”
小Hi回答道:“一个字符串中连续的一段就是这个字符串的子串,而回文串指的是12421这种从前往后读和从后往前读一模一样的字符串,所以最长回文子串的意思就是这个字符串中最长的身为回文串的子串啦~”
小Ho道:“原来如此!那么我该怎么得到这些字符串呢?我又应该怎么告诉你我所计算出的最长回文子串呢?
小Hi笑着说道:“这个很容易啦,你只需要写一个程序,先从标准输入读取一个整数N(N<=30),代表我给你的字符串的个数,然后接下来的就是我要给你的那N个字符串(字符串长度<=10^6)啦。而你要告诉我你的答案的话,只要将你计算出的最长回文子串的长度按照我给你的顺序依次输出到标准输出就可以了!你看这就是一个例子。”
样例输入
3
abababa
aaaabaa
acacdas
样例输出
7
5
3
最长回文子串:一个字符串中连续的一段就是这个字符串的子串,而回文串指的是12421这种从前往后读和从后往前读一模一样的字符串,所以最长回文子串的意思就是这个字符串中最长的身为回文串的子串啦!
方法一暴力法(60分)
遍历字符串S的每一个子串,去判断这个子串是不是回文,是回文的话看看长度是不是比最大的长度maxlength大。遍历每一个子串的方法要O(N2),判断每一个子串是不是回文的时间复杂度是O(N),所以暴利方法的总时间复杂度是O(N3)。
方法二中心扩展法(80分)
这个算法思想其实很简单啊,时间复杂度为O(N2),空间复杂度仅为O(1)。就是对给定的字符串S,分别以该字符串S中的每一个字符C为中心,向两边扩展,记录下以字符C为中心的回文子串的长度。但是有一点需要注意的是,回文的情况可能是 a b a,也可能是 a b b a。
预处理:在每两个相邻字符中间插入一个分隔符,一般可以用‘#’分隔。这样就非常巧妙的将奇数长度回文串与偶数长度回文串统一起来考虑了
代码:
/************************************************************************/
/*中心字符遍历,求解最长回文字符串! */
/************************************************************************/
#include<iostream>
#include<string.h>
usingnamespace std;
/**计算以char*str为中心的回文字符串的长度,返回回文字符串的长度**/
intReturnStrLenth (char* str)
{
int strlenth=1;
int i=1;
while(*(str-i)==*(str+i)&&*(str-i)!='$'&&*(str+i)!='$')
{
strlenth+=2;
i++;
}
return strlenth;
}
/**找到str字符串中最长的回文字符串的长度,返回最长长度值**/
intFindMaxReturnStr (char *str)
{
int strlenth=0;
int Maxlenth=0;
char flag;
str++;
while(*str!='$')
{
strlenth=ReturnStrLenth(str);
if (strlenth>Maxlenth)
{
Maxlenth=strlenth;
flag=*(str-strlenth/2);
}
str++;
}
if (flag=='$'||flag=='#')
{
Maxlenth=Maxlenth/2;
}
else
{
Maxlenth=Maxlenth/2+1;
}
return Maxlenth;
}
/**初始化string,在首位插入‘$’,字符间插入‘#’,返回插入后的字符串**/
char*InsertCharToString ( char* string)
{
char* NewString;
int i=0;
int strlenth=strlen(string);
NewString=new char[2*strlenth+2];
NewString[0]='$';
while (i<strlenth)
{
NewString[2*i+1]=string[i];
NewString[2*i+2]='#';
i++;
}
NewString[2*i]='$';
NewString[2*i+1]='\0';
return NewString;
}
intmain ()
{
int num,Maxlenth;
char str[1000000];
char *newstring;
cin>>num;
while(cin>>str)
{
newstring=InsertCharToString(str);
Maxlenth=FindMaxReturnStr(newstring);
cout<<Maxlenth<<endl;
delete []newstring;
}
return 0;
}
方法三 Manacher算法。时间复杂度O(N)(100分)
这个算法有一个很巧妙的地方,它把奇数的回文串和偶数的回文串统一起来考虑了。这一点一直是在做回文串问题中时比较烦的地方。这个算法还有一个很好的地方就是充分利用了字符匹配的特殊性,避免了大量不必要的重复匹配。
算法大致过程是这样。先在每两个相邻字符中间插入一个分隔符,当然这个分隔符要在原串中没有出现过。一般可以用‘#’分隔。这样就非常巧妙的将奇数长度回文串与偶数长度回文串统一起来考虑了,然后用一个辅助数组P记录以每个字符为中心的最长回文串的信息。P[id]记录的是以字符str[id]为中心的最长回文串,当以str[id]为第一个字符,这个最长回文串向右延伸了P[id]个字符。
原串: w aa bwsw f d
新串: # w # a # a # b # w # s # w# f # d #
辅助数组P: 1 2 1 2 3 2 1 2 1 2 1 4 1 2 1 2 1 2 1
这里有一个很好的性质,P[id]-1就是该回文子串在原串中的长度(包括‘#’)。
现在的关键问题就在于怎么在O(n)时间复杂度内求出P数组了。只要把这个P数组求出来,最长回文子串就可以直接扫一遍得出来了。
那么怎么计算P[i]呢?该算法增加两个辅助变量id和mx,其中id表示最大回文子串中心的位置,mx则为id+P[id],也就是最大回文子串的边界。
然后可以得到一个非常神奇的结论,这个算法的关键点就在这里了:如果mx > i,那么
P[i] >= MIN(P[2 * id - i], mx - i)。
当 mx - i > P[j]的时候,以S[j]为中心的回文子串包含在以S[id]为中心的回文子串中,由于
i 和 j对称,以S[i]为中心的回文子串必然包含在以S[id]为中心的回文子串中,所以必有
P[i] = P[j]。
当 P[j] > mx - i的时候,以S[j]为中心的回文子串不完全包含于以S[id]为中心的回文子串中,但是基于对称性可知,也就是说以S[i]为中心的回文子串,其向右至少会扩张到mx的位置,也就是说
P[i] >= mx - i。至于mx之后的部分是否对称,就只能老老实实去匹配了。
由于这个算法是线性从前往后扫的。那么当我们准备求P[i]的时候,i以前的P[j]我们是已经得到了的。我们用mx记在i之前的回文串中,延伸至最右端的位置。同时用id这个变量记下取得这个最优mx时的id值。(注:为了防止字符比较的时候越界,我在这个加了‘#’的字符串之前还加了另一个特殊字符‘$’,故新串下标是从1开始的)
(下面的部分为图片形式)
代码:
/************************************************************************/
/* Manacher算法,求解最长回文字符串! */
/************************************************************************/
#include<iostream>
#include<string.h>
using namespace std;
inline int min(int a,int b){
returna<b?a:b;
}
/**找到str字符串中最长的回文字符串的长度,返回最长长度值**/
int FindMaxReturnStr (char *str)
{
intstrlenth=strlen(str);
intMaxlenth=0;
int*p=new int[strlenth+1];
inti,id,mx = 0;
memset(p,0,sizeof(p));
for(i=1;i<strlenth; i++)
{
if(mx > i )
p[i]= min( p[2*id-i], mx-i );
else
p[i]= 1;
for(;str[i+p[i]] == str[i-p[i]]; p[i]++) ;
if(p[i] + i > mx )
{
mx= p[i] + i;
id= i;
}
}
for(i=0;i<strlenth;i++)
{
if(Maxlenth<p[i])
Maxlenth=p[i];
}
delete[]p;
returnMaxlenth-1;
}
/**初始化string,在首位插入‘$’,字符间插入‘#’,返回插入后的字符串**/
char* InsertCharToString ( char* string)
{
char*NewString;
inti=1;
intstrlenth=strlen(string);
NewString=newchar[2*strlenth+4];
NewString[0]='$';
NewString[1]='#';
while(i<=strlenth)
{
NewString[2*i]=string[i-1];
NewString[2*i+1]='#';
i++;
}
NewString[2*i]='*';
NewString[2*i+1]='\0';
returnNewString;
}
int main ()
{
intnum,Maxlenth;
charstr[1000000];
char*newstring;
cin>>num;
while(cin>>str)
{
newstring=InsertCharToString(str);
Maxlenth=FindMaxReturnStr(newstring);
cout<<Maxlenth<<endl;
delete[]newstring;
}
return0;
}