KMP算法
Next数组 Next[i]表示:当在i位置失配时,i应该跳回的位置,显然Next[0]=-1
void getNext(char *p)
{
next[0]=-1;
int len=strlen(p);
int j=0;
int k=-1;
while(j<len-1)
{
if(k==-1||p[j]==p[k])
next[++j]=++k;
else k=next[k];
}
}
//优化后的kmp算法
void getNext(char* p)
{
next[0]=-1;
int len=strlen(p);
int j=0;
int k=-1;
while(j<len-1)
{
if(k==-1||p[j]==p[k])
{
if(p[++j]==p[++k])
next[j]=next[k];
else next[j]=k;
}
else k=next[k];
}
}
int kmp(char* t,char* p)
{
int n=strlen(t);
int m=strlen(p);
int i=0;
int j=0;
getNext(p);
while(i<n&&j<m)
{
if(j==-1||t[i]==p[j])
{
i++;
j++;
}
else{
j=next[j];
}
}
if(j==m)
return i-j;
else return -1;
}
Manacher(马拉车)算法
马拉车用于解决最长回文子串问题,重点是子串,而不是子序列,想了解最长回文子序列的可以看下这篇博客传送门。对于这种问题,当然最简单粗暴的方法就是暴力求解,但太暴力也不好,毕竟会TLE。所以对于求最长回文子串的问题有一种神奇的算法——马拉车算法,神奇就神奇在时间复杂度为O(n)。
我先说一下大概思路,就是用一个Len[i]数组去存第i个位置到mx位置的长度,然后用id记录上一次操作的位置,mx标记上一次的最长子串的最右端,然后依次去递推。不太好理解(花了好几个小时才懂),现在看不懂没关系,我尽量讲的详细点。
以hihoCoder的一道裸题为例,只要能AC就差不多懂了,题目链接:传送门
先定义一个字符串s = “ababc”,因为对于回文字符串来说有对称轴,比如说aba就是以b作为对称轴,那么当字符串长度为偶数的时候,对称轴就不是唯一的整数位了。所以我们要对原字符串
做一个预处理,在每个字符左右都加上一个特殊字符,这里我用’#’,那么处理后的字符串str就是#a#b#a#b#c#了,不管原字符串长度是奇数还是偶数,处理后的长度都变成偶数了。然后还需要在这个str的最前面加一个不同于之前的特殊字符的特殊字符,这里我用了’%’(因为在后面的while循环中在匹配字符的时候可能会越界)。这样预处理操作就完了,最后的字符串str就是%#a#b#a#b#c#了。
首先需要明白的是,我定义的mx是上一次操作的最长回文子串的最右端,我解释一下,比如说ababa,第一次对i=2操作的时候,会计算出他的最回文子串为aba(长度为3),则在这次操作结束后mx的位置就是4,也就是aba的下一位。而我定义的id是上一次操作的位置,也就是i的位置,还以刚才的例子为例,在对i=2操作结束后,id的位置就是2。姑且先这么理解,知道他是什么就行,后面再去思考。
Len数组里存的是第i个位置到mx位置的长度,比如说还是ababa,当i=2的时候,可以计算出aba是它此时的最长回文子串,那么mx的位置就是aba的下一位,那Len[2]存的就是mx - i了。最重要的就是Len数组,所以这点一定要弄明白,Len存的是,当前这个位置到它的最长回文子串的最右端的距离(也就是mx的位置)。在str字符串中,Len[i] = 2*Len[i] - 1(因为有特殊字符),在s字符串中Len[i] = mx - id + 1。
知道这些后,开始进入正题,先看下核心代码。
#include <iostream>
#include <cstring>
#include <cstdio>
#define MAX 110100
int Len[2*MAX];
char s[MAX];
char str[2*MAX];
int mx,id;
int length;
using namespace std;
void init()
{
int k=0;
str[k++]='$';
for(int i=0;i<length;i++)
{
str[k++]='#';
str[k++]=s[i];
}
str[k++]='#';
length=k;
}
int Manacher()
{
int sum=0;
mx=0;
for(int i=1;i<length;i++)
{
if(mx>i)
Len[i]=min(Len[2*id-i],mx-i);
else Len[i]=1;
while(str[i+Len[i]]==str[i-Len[i]])
Len[i]++;
if(i+Len[i]>mx)
{
mx=i+Len[i];
id=i;
}
sum=max(sum,Len[i]);
}
return sum-1;
}
int main()
{
while(~scanf("%s",s))
{
length=strlen(s);
init();
printf("%d\n",Manacher());
}
}
最小表示法
我们这里要i = 0,j = 1,k = 0,表示从i开始k长度和从j开始k长度的字符串相同(i,j表示当前判断的位置)
当我们str[i] == str[j]时,根据上面k的定义,我们的需要进行k+1操作
当str[i] > str[j]时,我们发现i位置比j位置上字典序要大,那么不能使用i作为开头了,我们要将i向后移动,移动多少呢?有因为i开头和j开头的有k个相同的字符,那么就执行 i = i + k +1
相反str[i] < str[j]时,执行:j = j + k +1
最终i和j中较小的值就是我们最终开始的位置
相反如果是最大表示法的话,我们就要求解字典序最大的字符串,那么我们只需要在执行第二或第三个操作时选择较大的那个位置较好了
int Get_Min()
{
int n=strlen(s);
int i=0,j=1;
int k=0;
int t;
while(i<n&&j<n&&k<n)
{
t=s[(i+k)%n]-s[(j+k)%n];
if(!t)
k++;
else{
if(t>0)
i+=k+1; //j+=k+1
else j+=k+1; //i+=k+1
if(i==j)
j++;
k=0;
}
}
return min(i,j);
}