这段时间要沉迷刷题一段时间了,就让优快云陪我一起吧!
一、题目大意
题目的意思很简单,就是给定字符串的最长回文子串。
二、题目思路以及AC代码
虽然题目意思很容易理解,也是一个经典的DP题目,但这道题却并不好AC,主要是给的数据量太大了,字符串的长度最长为1000000。
如果是最经典的最长回文子串的做法就是区间DP,时间复杂度是n2,一看时间,给了15s,好像还有戏,那么我们来看一下空间复杂度,对于经典的区间DP,也是n2,这就没办法了,1000G的内存,这肯定是不行的。
所以这道题肯定要用O(n)的方法来解。那么问题是如何将最长回文子串用O(n)的方法求解呢?这也是我先学的,名为Manacher算法,俗称马拉车算法。其实代码中最为主要的就是一个公式,其他的部分可以参照Manacher’s Algorithm(很短,大家不会花很长时间)。
对于其中的那个p[i]的公式,我觉得他讲述的还不够清楚,当然,这里也是为自己巩固一下。公式是这样的:
具体的代码实现部分是如下这样:
for (int i = 1; i < len; i++) {
p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
while (t[i + p[i]] == t[i - p[i]]) p[i]++;
if (mx < i + p[i]) {
mx = i + p[i];
id = i;
}
if (resLen < p[i]) {
resLen = p[i];
}
}
我觉得这段还是结合代码来讲解比较好,代码中的t就是形如’$#a#b#c…#b#'的字符串,对于上述公式,可以看到,首先比较mx和i,为什么要比较这两个呢?因为我们的目的是要实现一个时间复杂度为O(n)的算法,所以就尽可能的不重复的对一些值进行遍历,那么这里mx表示当前找到的回文串中最右边的那个值的索引,i表示当前遍历到的值,如果mx>i,则说明以i为中心的回文子串已经有一部分是知道的,其实就是跟i关于id对称的位置的p,设这个对称位置为j,那么就是p[j],但这里的p[j]可能会超过mx-i,那样就不能保证一定是回文子串了,就需要自行去遍历检验。这样说可能比较抽象,我通过实例来说明一下。
比如现在有字符串$#a#b#a#c#a#b#a#,那么当我扫描到i = 12(b)的时候,我此时的id = 8©,mx = 16,那么我首先比较mx和i,mx > i,说明新扫描的b被包含在之前的某个回文串中,也就是id - p[id]到id + p[id]中,那么说明我此时的回文串和与c对称的位置的回文串部分相同,所以我找到j = 2*id - i = 4的位置(左边的b),然后看他的p[j]是多少,发现是4,而且mx - i也是4,所以直接让p[i] = 4,就可以免去查找这几个地方的遍历了,就减少了时间复杂度。
下面给出AC代码,500ms
#include <iostream>
#include <string>
#include <cstring>
#include <algorithm>
#define MAXN 2000010
using namespace std;
int p[MAXN];
string str;
int Manacher(string s) {
int len = s.length();
string t = "$#";
for (int i = 0; i < len; i++) {
t += s[i];
t += '#';
}
int mx = 0;
int id = 0;
int resLen = 0;
len = t.length();
for (int i = 1; i < len; i++) {
p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
while (t[i + p[i]] == t[i - p[i]]) p[i]++;
if (mx < i + p[i]) {
mx = i + p[i];
id = i;
}
if (resLen < p[i]) {
resLen = p[i];
}
}
return resLen - 1;
}
int main()
{
int Case = 1;
while (cin >> str) {
if (str == "END") break;
cout << "Case " << Case++ << ": ";
cout << Manacher(str) << endl;
}
return 0;
}
如果有问题,欢迎大家指正!!!