广师OJ 2238 回文子串 解题报告

来源:互联网 发布:win7 64位安装sql 编辑:程序博客网 时间:2024/04/30 05:44
题目链接 http://114.215.99.34/#/enter/problem?pid=2238

回文子串

【问题描述】
回文串,就是从前往后和从后往前看都是一样的字符串。那么现在给你一个字符串,请你找出该字符串中,长度最大的一个回文子串。

【输入描述】
有且仅有一个仅包含小写字母的字符串,保证其长度不超过5000

【输出描述】
有且仅有一个正整数,表示最长回文子串的长度

【输入样例】
abccbxyz

【输出样例】
4


【题解】

方法1:

枚举子串的起点和终点,然后独立判断该子串是否为回文串,是的话就用它的长度来尝试更新答案。时间复杂度为O(n^3),会超时。

方法2:

枚举回文串的中间字符,然后判断其两侧对应的字符是否相同。注意回文串的长度分奇数和偶数两种情况。时间复杂度为O(n^2)

参考代码

#include<stdio.h>#include<string.h>#define maxn 5000char 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++;elsebreak;}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++;elsebreak;}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 5000char 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 5005char 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次方为128calc(); 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

原创粉丝点击