求解字符串的最长回文子串(Onlogn和On做法)
第一种做法是字符串哈希加上二分答案,第二种做法是manacher算法
方法一:通过保存正序哈希值和逆序的哈希值我们可以遍历整个字符串,当以字符i为中心时,二分回文串的长度,最终得到最大值,为了避免对讨论奇数、偶数长度的回文串的讨论,我们可以在字符之间添加一个特殊符号,如#,让所有情况变为奇数,便于处理。
下面给出算法竞赛进阶指南的一道例题,AcWing地址:https://www.acwing.com/problem/content/141/
如果一个字符串正着读和倒着读是一样的,则称它是回文的。
给定一个长度为 N 的字符串 S,求他的最长回文子串的长度是多少。
输入格式
输入将包含最多 30 个测试用例,每个测试用例占一行,以最多 1000000 个小写字符的形式给出。
输入以一个以字符串 END 开头的行表示输入终止。
输出格式
对于输入中的每个测试用例,输出测试用例编号和最大回文子串的长度(参考样例格式)。
每个输出占一行。
输入样例:
abcbabcbabcba
abacacbaaaab
END
输出样例:
Case 1: 13
Case 2: 6
AC代码:
#include <bits/stdc++.h>
using namespace std;
#define reset(x) memset(x, 0, sizeof(x))
#define Q_in_out \
ios::sync_with_stdio(false); \
cin.tie(0); \
cout.tie(0);
typedef long long int ll;
typedef long double ld;
typedef pair<int, int> P;
#define forl(l, r) for (int i = l; i < r; i++)
#define forr(r, l) for (int i = r; i >= l; i--)
const int N = 1e6 + 5;
const int modp = 99991;
const int hsp = 131;
const int inf = 0x3f3f3f3f;
const double eps = 1e-6;
const double pi = acos(-1.0);
const double e = 2.718281828459045;
char s[N*2];
ll hl[N*2],hr[N*2],p[N*2];
ll getl(int l,int r){ //获取正序哈希值
return hl[r]-hl[l-1]*p[r-l+1];
}
ll getr(int l,int r){ //获取逆序哈希值
return hr[r]-hr[l+1]*p[l-r+1];
}
int main() {
int cnt=1;
while (scanf("%s",s+1)&&strcmp(s+1,"END"))
{
int n=strlen(s+1);
for(int i=2*n;i>0;i-=2){
s[i]=s[i/2];
s[i-1]='#';
}
n*=2;
p[0]=1;
for(int i=1,j=n;i<=n;i++,j--){
hl[i]=hl[i-1]*hsp+s[i]-'a'+1;
hr[j]=hr[j+1]*hsp+s[j]-'a'+1; //倒着存哈希值
p[i]=p[i-1]*hsp;
}
int res=0;
for(int i=2;i<=n;i++){ //s[1]是#可以跳过
int l=0,r=min(i-1,n-i);
while (l<r)
{
int m=l+r+1>>1;
if(getl(i-m,i-1)!=getr(i+m,i+1)) r=m-1; //左右两端哈希值不一样,缩小答案
else l=m;
}
if(s[i-l]!='#') res=max(res,l+1); //观察回文串首尾是否为#,来推算回文串长度
else res=max(res,l); //如#c#两端是#,且l等于1,故回文长度为1,c#b#c,l=2,且两端不是#长度为l+1=3
}
printf("Case %d: %d\n",cnt++,res);
}
return 0;
}
预告:这里并不是对Manacher算法的讲解,而是吐槽以及指路
至于另一种On的高效率做法,Manacher算法(马拉车):这个算法思想主要是动态规划,计算当前字符为中心的回文子串时利用已计算出的回文子串的信息来减少计算量,
具体过程还是比较复杂的,并且仅仅使用文字也很难表述清楚,这里给出力扣官方的视频讲解,讲得很清楚,并且有图,很方便理解,推荐看这个视频(关于这个算法,上百度一搜,大多数讲解博客都没讲算法原理,上来就是告诉你有几个数组几个变量,以及变量意义,个人感觉等于没讲,别在看懂别人的代码上浪费时间,在懂了算法原理后代码实现就简单了)。
视频地址:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zui-chang-hui-wen-zi-chuan-by-leetcode-solution/
下面再给出实现算法的代码(和网上代码版本一致,可以算是一个模板代码了,一定要看懂原理再来看代码)
/*题目和第一种解法的题目是同一个*/
#include <bits/stdc++.h>
using namespace std;
#define reset(x) memset(x, 0, sizeof(x))
#define Q_in_out \
ios::sync_with_stdio(false); \
cin.tie(0); \
cout.tie(0);
typedef long long int ll;
typedef long double ld;
typedef pair<int, int> P;
#define forl(l,r) for(int i=l;i<r;i++)
#define forr(r,l) for(int i=r;i>=l;i--)
const int N = 1e6+5;
const int modp = 99991;
const int hsp = 131;
const int inf = 0x3f3f3f3f;
const double eps = 1e-6;
const double pi = acos(-1.0);
const double e = 2.718281828459045;
char s[N];
char s_new[2*N];
int p[2*N];
int init() {
int len=strlen(s);
s_new[0]='$';
s_new[1]='#';
int j=2;
for(int i=0;i<len;i++) {
s_new[j++]=s[i];
s_new[j++]='#';
}
s_new[j]='\0';
return j;
}
int Manacher() {
int len=init();
int res=-1;
int id;
int mr=0;
for(int i=1;i<=len;i++) {
if(i<mr)
p[i]=min(p[2*id-i],mr-i);
else p[i]=1;
while(s_new[i-p[i]]==s_new[i+p[i]])
p[i]++;
if(mr<i+p[i]) {
id=i;
mr=i+p[i];
}
res=max(res,p[i]-1);
}
return res;
}
int main(){
int cnt=1;
while (scanf("%s",s)&&strcmp(s,"END"))
{
printf("Case %d: %d\n",cnt++,Manacher());
}
return 0;
}