题目链接 http://114.215.99.34/#/enter/problem?pid=2238
回文子串
【问题描述】
回文串,就是从前往后和从后往前看都是一样的字符串。那么现在给你一个字符串,请你找出该字符串中,长度最大的一个回文子串。
【输入描述】
有且仅有一个仅包含小写字母的字符串,保证其长度不超过5000
【输出描述】
有且仅有一个正整数,表示最长回文子串的长度
【输入样例】
abccbxyz
【输出样例】
4
【题解】
方法1:
枚举子串的起点和终点,然后独立判断该子串是否为回文串,是的话就用它的长度来尝试更新答案。时间复杂度为O(n^3),会超时。
方法2:
枚举回文串的中间字符,然后判断其两侧对应的字符是否相同。注意回文串的长度分奇数和偶数两种情况。时间复杂度为O(n^2)
方法3:
既然题目要求最长回文子串的长度,不妨先从最大长度开始枚举,来作为子串的长度,然后枚举子串的起点,然后检验该子串是否为回文串。如果在该长度下找不到回文串,就枚举的长度递减1,再次重复前面的操作。这样枚举的好处是,一旦找到回文串就可以结束查找了。时间复杂度为0(n^2),效率比方法2要高一些。
参考代码
回文子串
【问题描述】
回文串,就是从前往后和从后往前看都是一样的字符串。那么现在给你一个字符串,请你找出该字符串中,长度最大的一个回文子串。
【输入描述】
有且仅有一个仅包含小写字母的字符串,保证其长度不超过5000
【输出描述】
有且仅有一个正整数,表示最长回文子串的长度
【输入样例】
abccbxyz
【输出样例】
4
【题解】
方法1:
枚举子串的起点和终点,然后独立判断该子串是否为回文串,是的话就用它的长度来尝试更新答案。时间复杂度为O(n^3),会超时。
方法2:
枚举回文串的中间字符,然后判断其两侧对应的字符是否相同。注意回文串的长度分奇数和偶数两种情况。时间复杂度为O(n^2)
参考代码
#include<stdio.h>
#include<string.h>
#define maxn 5000
char a[maxn];
void solve()
{
int ans = 0;
int len = strlen(a);
for(int mid = 0; mid < len; mid++)
{
int i;
int cnt = 0;
//考虑奇数回文串
for(i = 1; i <= len/2; i++)
{
if(mid-i>=0 && mid+i<len && a[mid-i] == a[mid+i])
cnt++;
else
break;
}
if(cnt*2+1 > ans) ans = cnt*2+1;
//考虑偶数回文串
cnt = 0;
for(i = 1; i <= len/2; i++)
{
if(mid-i+1>=0 && mid+i<len && a[mid-i+1] == a[mid+i])
cnt++;
else
break;
}
if(cnt*2 > ans) ans = cnt*2;
}
printf("%d\n", ans);
}
int main()
{
scanf("%s", a);
solve();
return 0;
}
方法3:
既然题目要求最长回文子串的长度,不妨先从最大长度开始枚举,来作为子串的长度,然后枚举子串的起点,然后检验该子串是否为回文串。如果在该长度下找不到回文串,就枚举的长度递减1,再次重复前面的操作。这样枚举的好处是,一旦找到回文串就可以结束查找了。时间复杂度为0(n^2),效率比方法2要高一些。
参考代码
#include<stdio.h>
#include<string.h>
#define maxn 5000
char a[maxn];
bool is_huiwen(char a[], int st, int ed)
{
while(st < ed)
{
if(a[st] != a[ed]) return false;
st++;
ed--;
}
return true;
}
void solve()
{
int len = strlen(a);
for(int L = len; L >= 1; L--) //降序枚举回文子串的长度
{
for(int st = 0; st+L-1 < len; st++) //枚举子串的起点
{
if(is_huiwen(a, st, st+L-1))
{
printf("%d\n", L);
return;
}
}
}
}
int main()
{
scanf("%s", a);
solve();
return 0;
}
方法4:
专门用来求回文串的manacher算法,该算法的特色之处是不需要考虑回文串长度的奇偶。时间复杂度是O(n)。
manacher算法介绍 http://www.61mon.com/index.php/archives/181/
代码转载
#include<stdio.h>
#include<string.h>
#define maxn 5005
char s[maxn];
char s_new[maxn << 1];
int p[maxn << 1];
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; //返回s_new的长度
}
int min(int a, int b)
{
return a < b ? a : b;
}
int max(int a, int b)
{
return a > b ? a : b;
}
int Manacher()
{
int len = Init(); //取得新字符串长度并完成向s_new的转换
int max_len = -1; //最长回文长度
int id;
int mx = 0;
for(int i = 1; i < len; i++)
{
if (i < mx)
p[i] = min(p[2 * id - i], mx - i); //核心公式,需要弄明白mx和2*id-i的含义
else
p[i] = 1;
while(s_new[i - p[i]] == s_new[i + p[i]]) //不需边界判断,因为左有'$',右有'\0'
p[i]++;
//我们每走一步i,都要和mx比较,我们希望mx尽可能的远,这样才能更有机会执行if (i < mx)这句代码,从而提高效率
if(mx < i + p[i])
{
id = i;
mx = i + p[i];
}
max_len = max(max_len, p[i] - 1);
}
return max_len;
}
int main()
{
scanf("%s", s);
printf("%d\n", Manacher());
return 0;
}
方法5:
把原字符串反转并把它接在原字符串的后面,中间加入一个字符串中不存在的字符作为分隔符(例如'$')。枚举每一位i,求以这一位为中心的最长回文子串是什么。
我们可以使用后缀数组来求解,注意回文子串为偶数和为奇数是两种情况。
对于奇数回文串,求后缀i和后缀2*n-i的最长公共前缀;对于偶回文串,求后缀i和后缀2*n-i-1的最长公共前缀。
时间主要花在求后缀数组上,时间复杂度是O(nlogn)。如果过程中使用到的RMQ算法用O(n)的方法预处理,可以进一步减少到O(n)。
参考代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAX_N = 5005;
int a[MAX_N << 1], r[MAX_N << 1], h[MAX_N << 1], n, l, sa[MAX_N << 1];
int ws[MAX_N << 1], wv[MAX_N << 1], wa[MAX_N << 1], wb[MAX_N << 1];
char s[MAX_N];
int rmp[33][MAX_N << 1]; //rmq[k][a]表示区间[a, a+2^k-1]的最小值
int lg[MAX_N << 1]; //lg[x]表示log2(x)
void da(int *a, int *sa, int n, int m)
{
int i;
int *x = wa, *y = wb;
for(i = 0; i < m; i ++) ws[i] = 0;
for(i = 0; i < n; i ++) ws[x[i] = a[i]]++;
for(i = 1; i < m; i ++) ws[i] += ws[i - 1];
for(i = n - 1; i >= 0; i --) sa[-- ws[x[i]]] = i;
for(int k = 1; k <= n; k <<= 1)
{
int p = 0;
for(i = n - k; i < n; i ++) y[p ++] = i;
for(i = 0; i < n; i ++)
if (sa[i] >= k) y[p ++] = sa[i] - k;
for(i = 0; i < n; i ++) wv[i] = x[y[i]];
for(i = 0; i < m; i ++) ws[i] = 0;
for(i = 0; i < n; i ++) ws[wv[i]] ++;
for(i = 1; i < m; i ++) ws[i] += ws[i - 1];
for(i = n - 1; i >= 0; i --) sa[-- ws[wv[i]]] = y[i];
swap(x, y);
p = 1;
x[sa[0]] = 0;
for (i = 1; i < n; i ++)
x[sa[i]] = (y[sa[i - 1]] == y[sa[i]]) && (y[sa[i - 1] + k] == y[sa[i] + k]) ? p-1 : p++;
if (p >= n) break;
m = p;
}
}
void calc()
{
int k = 0, j, i;
for (i = 1; i <= n; i ++) r[sa[i]] = i;
for (i = 0; i < n; h[r[i ++]] = k)
for(k ? k-- : 0, j = sa[r[i] - 1]; a[i + k] == a[j + k]; k++);
}
void RMQ()
{
lg[0] = -1;
for (int i = 1; i <= n; i ++)
lg[i] = (i & (i - 1)) == 0 ? lg[i - 1] + 1 : lg[i - 1];
for (int i = 1; i <= n; i ++)
rmp[0][i] = i;
for (int i = 1; i <= lg[n]; i ++)
for (int j = 1; j + ((1 << i) - 1) <= n; j ++){
int a = rmp[i - 1][j], b = rmp[i - 1][j + (1 << (i - 1))];
if (h[a] < h[b]) rmp[i][j] = a;
else rmp[i][j] = b;
}
}
int ask(int a, int b)
{
int k = lg[b - a + 1];
b -= (1 << k) - 1;
a = rmp[k][a], b = rmp[k][b];
return h[a] < h[b] ? a : b;
}
int lcp(int a, int b)
{
a = r[a];
b = r[b];
if (a > b) swap(a, b);
return h[ask(a + 1, b)];
}
void init()
{
scanf("%s", s);
l = strlen(s);
for (int i = 0; i < l; i ++) a[i] = (int)s[i];
a[l] = 1;
for (int i = 0; i < l; i ++) a[i+l+1] = a[l-i-1];
n = (l << 1) + 1;
a[n] = 0;
da(a, sa, n+1, 128); // 'z'的ASCII码为122,凑足2的7次方为128
calc();
RMQ();
}
void solve()
{
int ans = 0, st;
for (int i = 0; i < l; i ++){
int k = lcp(i, n - i);
if (2 * k > ans) ans = 2 * k, st = i - k;
k = lcp(i, n - i - 1);
if (2 * k - 1 > ans) ans = 2 * k - 1, st = i - k + 1;
}
//for (int i = st; i <= st + ans - 1; i ++) printf("%c", s[i]);
printf("%d\n", ans);
}
int main()
{
init();
solve();
return 0;
}
注:后缀数组的模板代码可参考 《算法竞赛入门经典训练指南(第1版)》刘汝佳 陈锋 第221页
更多情况可以搜索“后缀数组 回文串”
参考文章 http://blog.csdn.net/u013480600/article/details/23946429