马拉车算法

问题

现在针对一个字符串  s1 我们要找到他的最长回文子串,该如何去设计这个算法。

回文串:

就是一个字符串颠倒后与原来的串相同,比如   aabbcbbaa,这是个回文串,aabbaa这也是一个串

现在我们以“aabbcbbaa"为例子,要说这个串是回文串的一种判断方法是  以c为中心,向两边扩展,左右相等,判断是不是。

那么针对以上问题,我们可以枚举每个位置,以这个位置为中心,向外扩展,扩展到最大,维护一个最长的回文串,但是如果是偶数串的话,那么在没有中心我们要怎么办,方法是在每两个字符之间插入一个与问题毫无相关的字符

比如  串"aabbaa",在插入 ’&‘ 后变成 &a&a&b&b&a&a& ,这样串就变成了一个奇数串,这个新回文串长度为 13  原串长度为 6   

那么串"aabaa"同上述操作后变成”&a&a&b&a&a&"也是一个奇数串,新回文串长度为11,原串长度为5,

即原串的长度l1与新串长度l2的关系是  l1=l2/2;

对串进行上述变换的代码是

#include<bits/stdc++.h>

using namespace std;

signed main()
{
	string s; cin >> s;
	int n = 0;
	string a;
	a+= '^', a+= '&';
	for (int i = 0; i < s.size(); i++)
	{
		++n;
		a += s[i];
		++n;
		a += '&';
	}
	cout << a << endl;
	return 0;
}

上面得到的新串有效位置下标从1开始(小心点)

原串是s,新串是a(本文一下都是这样,提示一下,下文不做解释)

对于每个位置是O(n) 里面每次维护也是O(n),达到一个O(n*n)的复杂度

我们既然说了以每个位置向两端扩展到底怎么扩展呢,

比如 aabbaa->  $a$a$b$b$a$a$

    

 我们以箭头哪里开始  ,此位置的坐标是7  ,判断  a[7-1]==a[7+1]?  成立我们用一个 数  r(初始时是0)使 r++;  一直循环 到不成立  ,转换成代码使  

	int maxa = -0x3f3f3f3f;
	for (int i = 1; i <= n; i++)
	{
		int j = 1,r=0;
		while (i - j >= 1 && i + j <= n && a[i - j] == a[i + j])
		{
			++r;
			++j;
		}
		maxa = max(maxa, r);//维护一个最大回文串
	}


但是这个r最后肯定不是原串的最大 ,我们r/2,就是原串啦!!!你是不是这样想的,这个r的意义是回文串长度的一半,r*2得到的是新串中某个回文串的长度,在r*2/2的到的是原串的长度,即

新串中r即使原串的回文串长度

那整体的代码就是 

#include<bits/stdc++.h>

using namespace std;

signed main()
{
	string s; cin >> s;
	int n = 1;
	string a;
	a+= '^', a+= '&';
	for (int i = 0; i < s.size(); i++)
	{
		++n;
		a += s[i];
		++n;
		a += '&';
	}
	int maxa = -0x3f3f3f3f;
	for (int i = 1; i <= n; i++)
	{
		int j = 1,r=0;
		while (i - j >= 1 && i + j <= n && a[i - j] == a[i + j])
		{
			++r;
			++j;
		}
		maxa = max(maxa, r);//维护一个最大回文串
	}
	cout <<"输入的字符串的最大回文字串长度为  " << maxa << endl;
	return 0;
}

这个算法枚举每个中心,里层在扩展两边,复杂度肯定比o(n)大,现在如果说我们把上面枚举的每个中心都放到一个r数组里,记录下来

那么 aabaa ->$a$a$b$a$a$->r数组:0 1 2 1 0 5 0 1 2 1 0 

下面给出一个图解

在对以b为中心枚举回文串后,如果在往右边枚举下一个中心,在以b为中心向外扩展的里面  那么我们在以这个为中心扩展时,在b的左边是否以经进行了相同的操作

//马拉车模板
#include<bits/stdc++.h>

using namespace std;
int r[1000100];

signed main()
{
	string s; cin >> s;
	int n = 1;
	string a;
	a+= '^', a+= '&';
	for (int i = 0; i < s.size(); i++)
	{
		++n;
		a += s[i];
		++n;
		a += '&';
	}
	int maxa = -0x3f3f3f3f;
	int mid = 0, R = 0;
	for (int i = 1; i <= n; i++)
	{
		r[i] = (i <= R) ? min(r[2*mid - i], R - i + 1) : 1;
		while (a[i-r[i]] == a[i+r[i]])
		{
			++ r[i];
		}
		if (i + r[i] - 1 >= mid)
		{
			R = i + r[mid = i] - 1;
		}
		maxa = max(maxa, r[i]-1);
	}
	cout <<"输入的字符串的最大回文字串长度为  " << maxa << endl;
	return 0;
}

这个算法实际的复杂度是O(n)

  • 18
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值