• Manacher(马拉车)算法


    Manacher算法是一个求字符串的最长回文子串一种非常高效的方法,其时间复杂度为O(n)。下面分析以下其实行原理及代码:

    1.首先对字符串进行预处理

    因为回文分为奇回文和偶回文,分类处理比较麻烦,所以我们先要做一个预处理,在字符之间插入一个特殊字符(注意这个新插入的字符不能再原字符串中出现),这样无论原字符串是奇是偶,我们将它统一的转化为奇串。初此之外,我们还需在最开头也加一个特殊字符为了防止越界,例如:

    原字符串 s =” abbaTNTabcba ” ,则经过转化后这个字符串会转变为:

    sNew= “$#a#b#b#a#T#N#T#a#b#c#b#a#”

    2.引入一个和处理过后长=长度相同的数组记录

    引入一个数组假设计为p[snew.length()],其中 p[i] 表示以snew[i]为中心,半径为p[i]的最长回文子串,p[i]=1 则表示该回文子串就是senw[i]本身,下面是snew的最长回文子串半径:

    由上图可知,snew[20]='c' 为中心的最长回文子串半径为6,由于第一个和最后一个字符都是#号,且也需要搜索回文,为了防止越界,由于字符串在结尾有’’,所以在字符串开头需要加上非#号字符(为了区分这里用的$)。通过p数组可以找到最大回文子串半径的最大值及其中心位置,就能确定最长回文子串了。所以现在问题转化为求p数组。

    3.p数组(最长回文子串半径数组)的求法

    Manacher算法利用开头提到的回文的左边是右边的镜像,让回文串起始的对比位置尽可能的大:

    这里引入了两个新的变量id和mx,id为最大回文串中心的位置,mx为最大回文串的右边界,i为当前遍历带字符串的为位置。

    这里分两种讨论:

    一、mx > i

    假设当前遍历到字符串的位置i,由于在遍历到id位置的时候已知最大回文子串,位置i还在上一个最大回文子串的范围内,所以可以利用其镜像认为,位置i以id为中心镜像到另一边的位置j是对等的。 在mx>i的条件下,又分为以下两种情况:

    <1.mx - i > p[j] (图1)

    此时,以j为中心的回文子串包含在以id为中心的回文子串内,由于i和j位置对等,所以以i为中心的回文子串包含在以id为中心的回文子串内,所以p[i] = p[j] = p[2 * id - i]。

    <2. mx - i <= p[j] (图2)

    此时,以j为中心的回文子串超过了以id为中心的回文子串边界,但是由于i和j位置对等,绿框部分还是相同的。所以其向右延伸的范围最大就是mx-i,剩下超过的部分谁也不能保证是否一致,只能通过循环对比判断,所以p[i] = mx - i。  

    二、mx < i

    此时镜像位置对预判位置起不到作用,只能从长度为1进行对比,所以此时p[i]=1; 

    下面为实现代码:

    #include<string>
    #include <vector>
    #include<iostream>  
    #include <algorithm>
    
    using namespace std;
    
    string Manacher(string s)
    {
    	string snew="$#";			//预处理,只要在原字符串中不可能出现即可
    	int len=s.length();
    	for (int i=0;i<len;i++)
    	{
    		snew+=s[i];
    		snew+="#";
    	}
    	int len2=snew.length();
    	int lenans=-1;		    // 最长回文子串的长度
    	int pos=-1;				// 最长回文子串中心点的位置
    	vector<int> p(len2, 0);
    	int id=0;					//当前中心点的位置
    	int mx=0;					//最大回文子串的右边界
    	for (int i=1;i<len2;i++)
    	{
    		if (i<mx)
    			p[i]=min(p[2*id-i],mx-i);
    		else
    			p[i]=1;
    		while(snew[i-p[i]]==snew[i+p[i]])  //最左边sNew[0]='$',最右边sNew[sNew.size()] = '',无需判断边界
    			p[i]++;
    		if(p[i]+i>mx)  
    //我们每走一步i,都要和mx比较,我们希望mx尽可能的远,这样才能更有机会执行if(i<mx)这句代码,从而提高效率 
    		{
    			id=i;
    			mx=i+p[i];
    		}
    		if (p[i]-1>lenans)
    		{
    			lenans=p[i]-1;
    			pos=i;
    		}
    	}
    	string::iterator iStart=s.begin()+(pos-lenans-1)/2;  
        				//将最长回文子串起始位置转换回原串
    	return string(iStart,iStart+lenans);						   
        				//也可以return lenans,最长回文子串的长度
    }
    
    int main()
    {
    	string s;
    	cin >> s;
    	cout << s << " 的最长回文子串为: " << Manacher(s) << endl;
    	return 0;
    }
    
  • 相关阅读:
    一些资源的网址
    AppWidgetProvider 应用
    Android之Broadcast, BroadcastReceiver(广播) (转载)
    JVM的内存机制
    地理数据库、数据集和要素类
    博客开通了,我要记录自己成长路上的收获
    JSP8
    JSP4(内置对象)
    JSP7(Cookie与javamail)
    JSP6(JSP 指令与JSP 动作元素)
  • 原文地址:https://www.cnblogs.com/StungYep/p/12251903.html
Copyright © 2020-2023  润新知