这两道题都是求最长回文的长度,在这里我已开始想着就是没有思路,想了半天,然后翻了会儿书,找到了一个知识点,那就是manacher算法
这个算法很好的解决了这道难题,然后我子啊百度上边找啊找,找到了这个,最后也算是把这道题给理解了吧。
- 这道题主要是解决一个问题,那就是如果长度是计数或者偶数结果是不一样的,但是这个算法很巧妙地解决了这个难题,正如我给的那个链接说的一样,
-
首先:大家都知道什么叫回文串吧,这个算法要解决的就是一个字符串中最长的回文子串有多长。这个算法可以在O(n)的时间复杂度内既线性时间复杂度的情况下,求出以每个字符为中心的最长回文有多长,
这个算法有一个很巧妙的地方,它把奇数的回文串和偶数的回文串统一起来考虑了。这一点一直是在做回文串问题中时比较烦的地方。这个算法还有一个很好的地方就是充分利用了字符匹配的特殊性,避免了大量不必要的重复匹配。
算法大致过程是这样。先在每两个相邻字符中间插入一个分隔符,当然这个分隔符要在原串中没有出现过。一般可以用‘#’分隔。这样就非常巧妙的将奇数长度回文串与偶数长度回文串统一起来考虑了(见下面的一个例子,回文串长度全为奇数了),然后用一个辅助数组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]的时候,i以前的P[j]我们是已经得到了的。我们用mx记在i之前的回文串中,延伸至最右端的位置。同时用id这个变量记下取得这个最优mx时的id值。(注:为了防止字符比较的时候越界,我在这个加了‘#’的字符串之前还加了另一个特殊字符‘$’,故我的新串下标是从1开始的)
好,到这里,我们可以先贴一份代码了。
复制代码
void pk()
{
int i;
int mx = 0;
int id;
for(i=1; i<n; 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;
}
}
}
代码是不是很短啊,而且相当好写。很方便吧,还记得我上面说的这个算法避免了很多不必要的重复匹配吧。这是什么意思呢,其实这就是一句代码。
if ( mx > i )p [ i ] = MIN ( p [ 2 * id - i ], mx - i );
就是当前面比较的最远长度mx>i的时候,P[i]有一个最小值。这个算法的核心思想就在这里,为什么P数组满足这样一个性质呢?(下面的部分为图片形式)
看完这个算法,你有可能会觉得这种算法在哪会用到呢?其实回文串后缀数组也可以做。只是复杂度是O(n log n)的,而且一般情况下也不会刻意去卡一个log n的算法。可正好hdu就有这么一题,你用后缀数组写怎么都得T(当然应该是我写得太烂了)。不信的话大家也可以去试试这题。
-
当然后边就是我自己的算法:
-
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <set>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <sstream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <string>
#include <cstring>
#include <cmath>
#include <ctime>
#define MAX 100000000
#define LOCA
#define PI acos(-1.0)
#include<stack>
char b[30000];
using namespace std;
char str[1000001], s[2000002];
int len, cas = 0, p[2000002];
void build()//在这里就是把一个个的s字符串写成一个接一连二的形式,方便不用考虑技术与欧舒,也就是这个才使得这个算法特别的巧妙,也是我们应该要学习的地方,你,加油哦!@-@
{
int i;
s[0] = '@';
s[1] = '#';
len = strlen(str);
for(i = 0; i < len; i++)
{
s[2 * i + 2] = str[i];
s[2 * i + 3] = '#';
}
s[2 * len + 2] = '\0';
}
void solve()
{
int i, mx = 0, id, ans = 1;
len = 2 * len + 2;
int xx=0;
for(i = 1; i < len; i++)
{
if(mx > i) p[i] = min(p[2 * id - i], mx - i);
else p[i] = 1;
for(; s[i - p[i]] == s[i + p[i]]; p[i]++)
if(p[i] + i > mx)
{
mx = p[i] + i;
id = i;
}
if(ans<p[i]){ans=p[i];xx=i;};
}
printf("Case %d: %d\n", ++cas, ans - 1);
}
int main()
{
while(scanf("%s", str) != EOF)
{
if(strcmp(str, "END") == 0)
break;
build();
solve();
}
return 0;
}
//dabcbab
//babccbad -
我在这里最后解释下怎么把这个最长的回文串给输出出来:
-
int t=0;
stack<int> p;//说明一下,这个xx就是那个最长的回文串的中心那个位置的下标,怎么对那个xx赋值,只需要在ans=max(ans,p[i])改成if(ans<p[i]){ans=p[i];xx=i;}即可
while(t<ans-1)//这个ans-1就是那个最长的回文串的长度
{
if(s[xx]=='#')//其实我在这里把他考虑复杂很多,其实根本没必要使用栈,用一个简单的数组就行,或许你/会觉得我这个看起来很傻,不会用脑子,能偷懒怎么就不偷一点呢,但是我们是在学习,学习这个只是为了懂得更多的知识点,因为我现在才大一,对于数据结构也就只是在算法里边见过,掌握的也不是很深,所以想着还是多找几个地方练练手,毕竟这个不是为了比赛,只是为了学到更多的东西,学的更熟练。这其实就是个过程,慢慢学吧!
{
p.push(s[t+1+xx]);//使用栈把id前边的回文串数据全都储存起来,但是这里储存的时候就要分那个中心到底是以‘#’为中心还是以字母为中心呢?这样岂不是不一样的,需要分开考虑!
// cout<<s[t+1+xx];
}
else
p.push(s[xx+t]);
// cout<<s[xx+t];
t+=2;
}
int k=0;
memset(b,0,sizeof b);
while(!p.empty())//将占里边的数据反向输出来,这就是输出为回文串里边的左边的字母
{
cout<<(b[k++]=p.top());
p.pop();
}
if(s[xx]=='#')xx=0;//然后输出中心右边的数据,这里我可以直接输出的
else xx=1;
while(xx<=k-1)
{cout<<b[k-1-xx];
xx++;
}
cout<<endl;
}