马拉车算法查找最长回文字符串

传统方法求最长回文字符串 时间复杂度(O(n^{2}))

#include <iostream>
using namespace std;
#include <cstring>
#include <cstdio>
#include<algorithm>
char b[110001];
int HUIWEN(int n)
{
	int j=n-1;
	int i=0;
	while(i<n)
	{
		if(b[i]==b[j])
		{
			i++;
			j--;
				
		}
		else 
		
		return 0;
		
		
	}
return n;
}
	
	
	int main()
	{
		
		
		while (scanf ("%s",b) != EOF)
		{
			int res=0;
	
	    int n=strlen(b);
		for(int i=1;i<=n;i++)
		{
			res=max(HUIWEN(i),res);
		}

	 cout<<res<<endl;
			memset(b,0,sizeof(b));
		}
		
		
		
		return 0;
	}

中心扩展算法

由外部向最内部比较 ——>由中心外部比较  减少一次遍历

可以用这种方法提高效率:

回文串一定是对称,所以我们可以每次循环都选择一个中心,进行左右扩展,判断左右字符是否相等即可。由于既存在奇数的字符串又存在偶数的字符串,所以要么中心从一个字符开始,要么从两个字符的中间开始。所以一共有n+n-1个中心 (从最左到最右)

所以代码的思路是遍历每个中心,然后判断对称位置是否相等。

时间复杂度 (O(n^{2}))(两层循环每次循环都是遍历每个字符 )

空间复杂度O(1)

#include  <iostream>
#include <string>
#include <algorithm>
using namespace std;
int expandAroundCenter(string s, int left, int right) {
    int L = left, R = right;
    while (L >= 0 && R < s.size() && s[L] == s[R]) {
        L--;
        R++;
    }
    return R - L - 1;
}





string longestPalindrome(string s) {
    if (s.empty() || s.size() < 1) return "";
    int start = 0, end = 0;
    for (int i = 0; i < s.size(); i++) {
        int len1 = expandAroundCenter(s, i, i); //从一个字符扩展
        int len2 = expandAroundCenter(s, i, i + 1); //从两个字符之间扩展
        int len = max(len1, len2);
        //根据 i 和 len 求得字符串的相应下标
        if (len > end - start) {
            start = i - (len - 1) / 2;
            end = i + len / 2;
        }
    }
    return s.substr(start, end + 1);
}

 



int main()
{
	string n;
	cin>>n;
cout<<	longestPalindrome(n);
	return 0;
}

传统方法反思

要分奇数和偶数进行考虑

没有思考利用前面查找工作的结果

没有思考会问字符本身的特性——对称性

所以我们的重头戏来了

 马拉车算法(Manacher)

在原字符串的每个相邻两个字符中间插入一个分隔符,同时在收尾也要加入一个分隔符,可以插入‘#’

经过这样转换 永远都是一个奇数 这样对称中心就是唯一的

string charu(string s)//首先构造出新的字符串
{
string Str ="*#";//以*#开头,#结尾 为了方便先在#前面打一个无关字符 如果不这样最后max(p)会出问题 
   for(int i=0;i<s.size();i++)
   {
    Str+=s[i];
    Str+='#';
   }
  
  return Str;
}

之后就是马拉车的精髓部分

一开始我也是不太懂

经过上面插入特殊字符后,我们再求得其长度,这时候

我们定义p[i]表示i位置的最长回文子串的长度 

例如字符串abbbba 插入字符后为*#a#b#b#b#b#a# 长度为14

 用P的下标i减去P[i]再除以2,就是原字符串的开头下标。

比如abbbba中的第2个b序号为2 等于(6-3)/2=1 所以以这个b为中心的最大回文字符串的起点下标为1 则想将其输出 只需要输出字符串的第1位到第3(i-1)位 即 “bbb”

但是很明显这并不是最长的回文字符串,我们要用回文串的对称性将每个P[ i ](i位置的最长回文子串的长度 )求出来

我们用C表示回文串的中心,用R表示回文串的右边半径。所以R=C+P[i]

C和R所对应的回文串是当前循环中R最靠有的回文串

让我们考虑求P[i]的时候

用mirrori表示当前需要求的第i的字符关于C对应的下标

由对称性P[i]=P[mirrori] 例如当C为7 P[6]=P[8]=3

 但是有以下三种情况不适合直接用对称性

1.超出了R

当C=8 P[6]与P[10]对称 为什么值不相等呢 因为C往右加上6等于14

最右边的回文字符串为bbb所以R为最后一个b右边一位为11 而14大于11 所以不行

但是我们P[i]至少可以达到R-i

会不会更大 我们只需要对比R以后的点和其关于i对称的点是否相等

就像暴力从中心向两边扩展一样

2.P[mirrori碰到了原字符串的左边界

比如当miorrori=2时候  P[2]与P[6]对称 但是P[2]!=P[6]

 原因是P[mirrori]在扩展时候首先是“#”=“#”,之后遇到“*”和另一个字符到达边界终止循环

而P[i](右边的)对称点并没有到达边界,所以我们可以继续通过中心扩展法一步一步向两边扩展

就可以了

3.i等于了R

此时我们把P[0]赋值为0,再通过中心法扩展一步一步向两边扩展

就可以了

考虑C和R的更新

当如上面方法一步一步的求出每个P[i],当求出的P[i]右边界大于当前的R时,我们就需要更新C和R为当前的回文串了,因为我们要保证i在R里面,所以一旦有更右边的R就要更新R

 此时发现经计算P[mirrori] 应该得3 所以此时C应该变为i=10 半径变为i+P [ i ]=13 然后继续下边的循环

#include <iostream>
using namespace std;

string charu(string s)//首先构造出新的字符串
{
string Str ="*#";//以*#开头,#结尾 为了方便先在#前面打一个无关字符 如果不这样最后max(p)会出问题 
   for(int i=0;i<s.size();i++)
   {
    Str+=s[i];
    Str+='#';
   }
  
  return Str;
}
string malache(string s)
{
string t=charu(s);
int n=t.size();
int P[n];
int C=0,R=0;
for(int i=1;i<n;i++)
{
	int mirrori=2*C-i;
	if(R>i)
	{
		P[i]=min(R-i,P[mirrori]);
		
	}
	else 
	{
		P[i]=0;
	}
	
	while(t[i+1+P[i]]==t[i-1-P[i]])
	{
		P[i]++;

	}
	if(i+P[i]>R){
		C=i;
		R=i+P[i];
	}
		
}
int maxlen=0;
int centerindex=0;
for(int i=1;i<n-1;i++)
{
	if(P[i]>maxlen)
	{
		maxlen=P[i];
		centerindex=i;
	}
}
int start=(centerindex-maxlen)/2;
return s.substr(start,start+maxlen);
}


int main()
{
   string a;
	   cin>>a;
	cout<<malache(a);
   return 0;
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白开水为啥没味

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值