//中点扩散算法
#include
using namespace std;
const int N=1e3+10;
int dp[N][N];
int main()
{
string str;
getline(cin,str);
int res=0;
for(int i=0;i<str.length();i++)
{
int l=i-1,r=i+1;//判断奇数长度回文串
while(l>=0&&r<str.length()&&str[l]==str[r]) l--,r++;
res=max(res,r-l-1);
l=i,r=i+1;//判断偶数长度回文串
while(l>=0&&r<str.length()&&str[l]==str[r]) l--,r++;
res=max(res,r-l-1);
}
cout<<res<<endl;
return 0;
}
(2)当str[ i ]!=str[ j ]时,dp[ i ][ j ]=0。
d p [ i ] [ j ] = { d p [ i + 1 ] [ j − 1 ] s t r [ i ] = = s t r [ j ] 0 s t r [ i ] ! = s t r [ j ] dp[i][j]=\begin{cases}dp[i+1][j-1]&str[i]==str[j]\\0&str[i]!=str[j]\end{cases} dp[i][j]={dp[i+1][j−1]0str[i]==str[j]str[i]!=str[j]
在遍历方面也要注意,如果按照i和j从小到大的顺序来枚举子串的两个端点,然后更新dp[i]lj],会无法保证dp[i + 1][ - 1]已经被计算过,从而无法得到正确的dp[i][i]。我们可以遍历长度,长度从小到达依次增加,每次判断出左右端点即可。代码如下:
//动态规划
#include
using namespace std;
const int N=1e3+10;
bool dp[N][N];
int main()
{
string str;
getline(cin,str);//读入字符串
int res=1;
int n=str.length();
for(int i=0;i<n;i++)
{
dp[i][i]=true;
if(i<n-1&&str[i]==str[i+1])//判断边界条件
{
res=2;
dp[i][i+1]=true;
}
}
for(int l=3;l<=n;l++)//遍历字符串长度,最小从3开始(最小为1,加上左右两边)
{
for(int i=0;i+l-1<n;i++)//左边界
{
int j=i+l-1;//右边界
if(str[i]==str[j]&&dp[i+1][j-1])
{
dp[i][j]=1;
res=l;//更新时,一定l>=res
}
}
}
cout<<res<<endl;
return 0;
}
题目链接
这道题的数据范围较大,不能采用前几种做法。可以用hash+二分来做,可以用二分来做的原因是,回文串存着一种单调性,通过hash可以方便的判断出两个字符串是否相等。遍历每个字符,然后二分其回文串能最长长度,hash判断其字符串是否相等。
在存hash值时,可以存一个正向的hash数组和一个逆向的hash数组。
为了方便代码书写,可以在字符串两两之间添加一个未出现的字符,这样就不用在写代码时分别讨论回文串长度奇偶的情况。
整体算法的时间复杂度为O(n log n)。
代码如下:
#include
using namespace std;
typedef unsigned long long ull;
const int N=1e6+10;
const int base=131;
ull hr[2*N],hl[2*N],p[2*N];//超过ull自动取余
char s[N*2];
ull get_hash(ull h[],int l,int r)//获取某段长度字符串的hash值
{
return h[r]-h[l-1]*p[r-l+1];
}
int main()
{
int t=1;
while(scanf("%s",s+1),strcmp(s+1,"END"))
{
int res=0;
int n=strlen(s+1);
for(int i=2*n;i>0;i-=2)
{
s[i]=s[i/2];
s[i-1]='z'+1;//添加#(未出现字符)
}
n*=2,p[0]=1;
for(int i=1,j=n;i<=n;i++,j--)
{
hl[i]=hl[i-1]*base+s[i]-'a'+1;//处理hash值
hr[i]=hr[i-1]*base+s[j]-'a'+1;
p[i]=p[i-1]*base;
}
for(int i=1;i<n;i++)
{
int l=0,r=min(i-1,n-i),mid;//二分寻找以str[i]为中心的最长回文子串的长度
while(r>l)
{
mid=l+r+1>>1;
if(get_hash(hl,i-mid,i-1)==get_hash(hr,n-(mid+i)+1,n-(i+1)+1))
l=mid;
else r=mid-1;
}
if(s[i-l]=='z'+1)
res=max(res,l);
else res=max(res,l+1);
}
printf("Case %d: %d\n",t++,res);
}
return 0;
}
参考视频链接
参考博客
例题链接
给定一个长度为 n 的由小写字母构成的字符串,求它的最长回文子串的长度是多少。
输入格式
一个由小写字母构成的字符串。
输出格式
输出一个整数,表示最长回文子串的长度。
数据范围
1≤n≤1e7
输入样例:
abcbabcbabcba
输出样例:
13
这道题字符串长度较大,明显是用O(n)时间复杂度的manacher算法,manacher算法具体过程可以看看上面的视频和博客,大佬介绍非常清楚,易于理解。我的理解是manacher算法是对DP和回文串对称性的利用。
代码如下:
#include
using namespace std;
int manacher(string str)
{
if(!str.length()) return 0;
int l=(int)(str.length()*2+1);
char *s=new char[l];//记录回文子串
int *p=new int[l];//记录每个位置最大回文半径
int r,c,index,mx;
r=c=-1;
index=mx=0;
for(int i=0;i<l;i++) s[i]=i&1?str[index++]:'#';
for(int i=0;i<l;i++)
{
p[i]=r>i?min(r - i, p[2 * c - i]):1;
while(i+p[i]<l&&i-p[i]>-1)
{
if(s[i+p[i]]==s[i-p[i]]) p[i]++;
else break;
}
if(i+p[i]>r)
{
r=i+p[i];
c=i;
}
mx=max(mx,p[i]);
}
delete[] s;
delete[] p;
return mx-1;
}
int main()
{
string str;
cin>>str;
cout<<manacher(str)<<endl;
return 0;
}
模板题:hdu3068上面的代码稍微改一下就能过。