求取最长回文字符串,o(n)的最优算法manacher

算法的第一步就是在每个字符的左右都加上一个#,这样有什么效果呢。

比如aba初始化之后为#a#b#a#,字符串长度为7是奇数。

比如1221初始化之后为#1#2#2#1#,字符串长度为9是奇数。

为什么我们要将其转换成奇数呢,因为算法求取回文串长度的时候,需要有一个中心节点,之后分别向左右搜索,所以需要将回文串豆转换为奇数长度。

之后我们需要将str[0]赋值为一个字符,可以赋值为$,不这样做也可以,但是这样做我们就可以从1开始处理字符串,大家知道C++的数组是从0开始的。

算法的第二步就进入了manacher算法的内部。这个算法之中其实就是利用回文串的对称性,做了一个优化,从而使得我们不用取一个一个的取计算。比如说s是一个回文字符串,我们计算其右半部分的一个节点的时候我们发现它复合回文串的特征,那么我们就知道s的左半部分一定也存在一个相同的节点,在s的范围内这个节点左右的元素都是相同的,但是有可能的是这个节点所代表的回文串会超出s字符串的长度,这个是一定会出现的。但是能省一部分工作就省一部分。

算法中有一个int型的数组,比如我们定义为 int p[200]; 那么p[i]的意思就是代表i节点左右回文字符串的长度。

算法中还有两个辅助变量centerID和maxEage,分别代表的是当前最长回文字符串的中心节点和其p[i]的值,也就是回文串的长度。

我感觉算法核心就在p[i] = Min(p[2*centerID-i],p[centerID]+centerID-i);,它之前会判断i是否在回文串中,如果在则继续判断以i为节点的回文串对称点j的p值和maxEage比较取最小的一个,因为只有在回文串中才能保证优化的可行性。如果i不在回文串中,那么将p赋值为1,左右一起搜索求出p值。

下面是源代码,里面还有一些注释,大家可以看一下。

<span style="font-size:14px;">#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;

#define Min(x, y) ((x)<(y)?(x):(y))

char inputString[100];//存放输入的原字符串
char testString[200];//存放初始化之后的字符串
int p[200];//存放testString中以每个元素为中心的会问字符串长度
int len;//字符串长度

void manacher()
{
    int i;
    int centerID,maxEage;
    //centerID代表最大回文字符串的中心
    //maxEage代表最大回文字符串的边界长度,也就是说2maxEage - 1就是字符串长度。
    maxEage = 0;
    centerID = 0;
    for (i = 1; i < len; i++)
    {
    		if (maxEage > i)//如果i在最长回文字符串的范围内
    		{
    			p[i] = Min(p[2*centerID-i],p[centerID]+centerID-i);
    			//j = 2*centerID-i为i的对称点
    			//其实比较的就是以i为中心的回文字符串是否在最大回文字符串内部,比较的就是p[j]和i到maxEage的距离。
    		}
    		else
    		{
    			p[i] = 1;//本身也算在内,所以最小长度为1
    		}

    		for(; testString[i+p[i]]==testString[i-p[i]]; p[i]++);

    		if (p[i] + i > maxEage)//如果找到更长的回文字符串,及时替换。
    		{
    			maxEage = p[i] + i;
    			centerID = i;
    		}
    }
    //因为字符串最中间的节点也maxEage内,所以提取左右边界时应该-1
}

void start()
{
	int i;
	len = strlen(inputString);
	testString[0] = '$';
	testString[1] = '#';
	for(i = 0; i < len; i++)
	{
		testString[i*2+2] = inputString[i];
		testString[i*2+3] = '#';
	}
	len = len*2 + 2;
	//cout<<testString<<endl;//可以验证初始化的字符串是否正确
	//cout<<len<<endl;//可以输出初始化字符串的长度
}

int main()
{
	int answer = 0,i,jiedian = 0;
	memset(p,0,sizeof(p));
	memset(testString,0,sizeof(testString));
	cin.getline(inputString,80);
	start();
	manacher();

	for(i=0;i<len;i++)
	{
	    if(p[i] > answer)
         {
            answer = p[i];
            jiedian = i;
         }
	}

    //cout<<jiedian<<endl;//可以定位最长回文字符串的中心点
    //cout<<p[jiedian]<<endl;

    cout<<"最长回文字符串:";
    for(i = jiedian - p[jiedian] + 1; i <= jiedian + p[jiedian] - 1; i++)
    {
        if(testString[i] == '#')
            continue;
        cout<<testString[i];
    }

	cout<<endl<<"字符串长度:"<<answer-1<<endl;
	return 0;
}
</span>
时间: 2024-10-12 03:02:23

求取最长回文字符串,o(n)的最优算法manacher的相关文章

最长回文字符串 POJ3974

曾经有一个好算法放到我面前,我没有好好珍惜,直到用到的时候才后悔莫及. 那就是Manacher(马拉车算法),以O(n)的复杂度计算最长回文字符串. 曾经刷Leetcode的时候,室友跟我说了这个算法,但当时那个题目用中间枚举也过了,我就没有在意,直到前天才弄会,写这篇报告之前, 我又专门写了一遍马拉车,果然还是有点问题的. 详细原理链接 点击 Mark #include <stdio.h> #include <iostream> #include <string.h>

【转】最长回文子串的O(n)的Manacher算法

Manacher算法 首先:大家都知道什么叫回文串吧,这个算法要解决的就是一个字符串中最长的回文子串有多长.这个算法可以在O(n)的时间复杂度内既线性时间复杂度的情况下,求出以每个字符为中心的最长回文有多长,    这个算法有一个很巧妙的地方,它把奇数的回文串和偶数的回文串统一起来考虑了.这一点一直是在做回文串问题中时比较烦的地方.这个算法还有一个很好的地方就是充分利用了字符匹配的特殊性,避免了大量不必要的重复匹配.    算法大致过程是这样.先在每两个相邻字符中间插入一个分隔符,当然这个分隔符

hdu3068 求一个字符串中最长回文字符串的长度 Manacher算法

最长回文 Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 31611    Accepted Submission(s): 11618 Problem Description 给出一个只由小写英文字符a,b,c...y,z组成的字符串S,求S中最长回文串的长度.回文就是正反读都是一样的字符串,如aba, abba等 Input 输入有多组

URAL 1297 求最长回文字符串

有种简单的方法,数组从左到右扫一遍,每次以当前的点为中心,只要左右相等就往左右走,这算出来的回文字符串是奇数长度的 还有偶数长度的回文字符串就是以当前扫到的点和它左边的点作为中心,然后往左右扫 这是O(n^2)的复杂度,这道题过还是没有问题的 这里我主要练习的是另外的利用后缀数组加RMQ算法来解决这个问题 大致思想跟上面一致 首先将字符串反转贴在原字符串末尾,将字符通过ASCII码转化为字符,之间用一个1分开,最后贴一个0 然后得到它的后缀数组以及height[]数组 那么找回文字符也是扫一遍,

csu-1328 近似回文词 和 最长回文字符串

原博文地址:http://blog.csdn.net/u012773338/article/details/39857997 最长回文子串 描述:输入一个字符串,求出其中最长的回文子串.子串的含义是:在原串连续出现的字符 串片段.回文的含义是:正着看和倒着看是相同的,如abba和abbebba.在判断是要求忽略所有的标点和空格,且忽略大小写,但输出时按原样输出(首 尾不要输出多余的字符串).输入字符串长度大于等于1小于等于5000,且单独占一行(如果有多组答案,输出第一组). 输入 :每行有一个

Java Longest Palindromic Substring(最长回文字符串)

假设一个字符串从左向右写和从右向左写是一样的,这种字符串就叫做palindromic string.如aba,或者abba.本题是这种,给定输入一个字符串.要求输出一个子串,使得子串是最长的padromic string. 下边提供3种思路 1.两側比較法 以abba这样一个字符串为例来看,abba中,一共同拥有偶数个字.第1位=倒数第1位.第2位=倒数第2位......第N位=倒数第N位 以aba这样一个字符串为例来看,aba中.一共同拥有奇数个字符.排除掉正中间的那个字符后,第1位=倒数第1

查找字符串中的最长回文字符串---Manacher算法

转载:https://www.felix021.com/blog/read.php?2040 首先用一个非常巧妙的方式,将所有可能的奇数/偶数长度的回文子串都转换成了奇数长度:在每个字符的两边都插入一个特殊的符号.比如 abba 变成 #a#b#b#a#, aba变成 #a#b#a#. 为了进一步减少编码的复杂度,可以在字符串的开始和结尾加入另一个特殊字符这样就不用特殊处理越界问题,比如%#a#b#a#@;(如果是C++,字符串末尾有一个\0,故结尾处不需要添加额为的特殊字符@) 然后用一个数组

寻找最长回文字符串

首先讲解一种简单容易理解的暴力解法:复杂度为O(n^2) 解题思路是:第一,定义一个pStr指向字符串str,再定义一个p指向pStr,q指向pStr+1: 第二,找出一个字符*p与其下一个字符*q相同位置,比如oo,num++,index = p:然后比较这两个相同字符*p,*q两边的字符是否相等,如果相等再向两边扩展p--,q++(p>str&&q!='\0').如果p指向首部,即p=str,则调出while循环,再比较一次if(*p == *q),num++,index = q

字符串中的最长回文字符串

“”“ 形如 abba,abaaba正反序相等的字符串称之为回文字符串 题源:领口 ”“” 1.用键值的形式去记录已经存在的字符和其序列 2.遍历原始的字符串 如果字典中没有则存入  如果有 则对其进行回文检测 3.若出现回文字符串,与现在的进行比较 #但是 单单以键值对形式存储 字符和序列  如原始字符串为 ‘abcacbae’ ,和‘abcaddedda’ 这种字符串 那么原先设计的程序就会崩溃 #要找到一个 判别当前字符串的在原字符串中的有效起始位置. kill:通过 键 列表 形式的键值