虽然已经学了三年计算机,但是感觉自己还是个代码小白吧。看见这个题是在队里vjudge的题上面。题目如下:
Q:回文串是指aba、abba、cccbccc、aaaa这种左右对称的字符串。输入一个字符串Str,输出Str里最长回文子串的长度。
INPUT:输入Str(Str的长度 <= 1000);//daabaac
OUTPUT:输出最长回文子串的长度L;//5
这题的最佳解当然是O(n)的Manache算法啦~Manache算法主要是阐释了一种替换的思想,成功把问题简化。
首先假如直接解这个题我们要考虑的是回文串要分奇数和偶数,但是Manache算法简化了这个过程,通过对字符串插入一批“独一无二的符号”,让我们不再需要考虑奇偶的问题。具体操作如下:
eg:str1:daabaac 我们可以通过在每两个字符之间插入一个'#',得到str2:#d#a#a#b#a#a#c#
这时候我们可以很容易的发现原来的奇偶两种回文串被转换成了只有奇数回文串的形式,这就是Manache算法的第一步。
第二步就是Manache算法的核心部分,也正是这种天马星空的想法造就了一个O(n)的经典算法;
这个时候我们要赋予str2的每个成员一个属性:p[i]以这个字符为中心所得到的最长回文串的半径的长度,然后用两个变量分别记录最大的回文字串的中心位置id和这个回文串的半径maxr。
接下来我们从字符串的头开始遍历。当我们有一个id和一个maxr的时候我们遍历到了一个位置为i的字符时,会产生两种情况:i在最大回文串中和i不在最大回文串中。
当i在最大回文串中时,我们可以知道i关于id有一个对称的i0存在(因为i在一个对称的回文串中,所以一定轴对称存在一个i0已知p[i0])这时如果i0的半径在回文串id内的时候,我们可以很容易的看出(想象出)p[i]=p[i0];
(因为轴对称)
但是如果i0的半径不在回文串id内的时候,我们已知目前p[i] = 2*id-i(i到回文串id边界的距离),然后逐个向右进行暴力检索判断是否为回文串。
当i不在回文串id内的时候我们就直接暴力搜索,判断左右是否相等来得到p[i]的最终值。
最后我们的p[i]中最大的值经过简单处理就是我们最后的结果啦~
ps:因为只从头到尾遍历了一次字符串,所以时间复杂度为O(n);
C++代码如下:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 3000;//
char s[maxn], str[maxn];//s:原始数组,str:填充数组
int len1, len2, p[maxn], ans;//p[]:manacher数组
void init() {//字符串填充
str[0] = '*';
str[1] = '#';
for(int i = 0; i < len1; i++) {
str[i * 2 + 2] = s[i];
str[i * 2 + 3] = '#';
}
len2 = len1 * 2 + 2;
str[len2] = '*';
}
void manacher() {
int id = 0, mx = 0;//id:最大回文字串中间位置,mx:最大回文字串右端点位置
for(int i = 1; i < len2; i++) {
if(mx>1)
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;
}
}
}
int main() {
while(cin >> s) {
len1 = strlen(s);
init();
manacher();
ans = 0;
for(int i = 0; i < len2; i++) {
ans = max(ans, p[i]);
}
cout << ans - 1 << endl;
}
}
(第一次写博客分享对算法的理解,如有不对希望大佬们指出= =
谢谢~