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

题目链接 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 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值