【算法题】Manacher算法及其扩展

来源:互联网 发布:java bigdecimal最大值 编辑:程序博客网 时间:2024/06/15 14:26

2017/11/21

Manacher问题


1、Manacher问题

1.1 问题描述:

找出字符串str中最长的回文子串

1.2 思路

1、在解决最长回文子串问题前,要解决奇回文和偶回文的问题。我们在判断奇回文时,是根据一个字符串,然后同时向两边扩展;偶回文则是直接向两边扩展,中间没有字符串。如下:

12a21 奇回文1221  偶回文

为了解决这个问题,将原始字符串进行改进,在原始字符串的开头、结尾以及字符之间添加一个字符,如“#”,作为一个虚轴。然后使用新的字符串。这样做不会改变原有的回文结构,而且新字符串中所有的回文变为奇回文。

eg:

"123abx""#1#2#3#a#b#x#"

2、然后是具体的Manacher算法,首先给出几个概念。

  • 回文边界R:字符串最右回文边界。

  • 回文中心C:字符串最右回文边界R的中心,随R变动。

  • 回文半径数组radius[]:记录每个字符以其为中心的回文半径大小。

3、Manacher一共分了四种情况讨论。

  1. 当遍历到i时,如果i在R的右边,则只能采取暴力匹配方式,直接以其为中心查找回文。

  2. 当遍历到i时,如果i不在R的右边,则又分为3种情况:

    ①i关于回文中心C的对称点i’的回文边界在C的回文边界中,则可知i的回文边界也在C的回文边界里,不会再向外扩,解释如图:

这里写图片描述

②i关于回文中心C的对称点i’的回文边界在C的回文边界外(左边界超出),则可知i的回文右边界刚好在C的回文右边界上,不会再向外扩,解释如图(图中左边是i’,不小心写错了):

这里写图片描述

③i关于回文中心C的对称点i’的回文边界在C的回文边界上(左边界与左边界重合),则可知i已有的回文在C的边界中,但是超过R之后,i是否有能更大的回文,无法得知,只能采用暴力扩的方式,解释如图:

这里写图片描述

4、所以综上,在代码处理上,一共分了4种情况,在情况2、3上其回文半径就是i’的回文半径radius[2 * C - i]与R - i其中较小的那一个。

1.3 代码

#include <iostream>#include <string>#include <vector>using namespace std;/*2017/11/18Manacher算法:找出字符串str中最长的回文子串*/#if 1#define max(a,b)(a>b?a:b)#define min(a,b)(a<b?a:b)string newString(string s){    string news = "#";    for (int i = 0; i < s.length();i++)        news = news + s[i] + "#";    return news;}//都用奇回文解决string Manacher(string s){    if(s.length()<2)        return s;    int C = -1;//最右回文半径的回文中心    int R = -1;//最右回文边界的下标,但不包括在回文中    int imax = INT_MIN;//最大回文半径    int ic = 0;//最大回文半径的回文中心    string news = newString(s);    vector<int>radius(news.length(), 0);    for (int i = 0; i < news.length(); i++)    {        radius[i] = 1;//默认从左右两边起第一个开始比较        radius[i] = R > i ? min(radius[2 * C - i], R - i) : radius[i];        while (i-radius[i]>=0 && i+radius[i]<news.length())//不能越过字符串边界        {            if (news[i - radius[i]] == news[i + radius[i]])//R>i不会进入,直接break                radius[i]++;            else                break;        }        if (i + radius[i] > R)        {            C = i;            R = i + radius[i];        }        if (radius[i] > imax)        {            imax = radius[i];            ic = C;        }    }    imax = imax - 1;//imax最后++时多加了一个1,要减掉,得到原字符串的最大回文长度    return s.substr((ic - imax) / 2, imax);}void main(){    string s1 = "abc1234321ab";    cout << Manacher(s1) << endl;    string s2 = "1234321ab";    cout << Manacher(s2) << endl;    system("pause");}#else#endif

1.4 结果截图

这里写图片描述

2、Manacher问题扩展

2.1 问题描述:

给定一个字符串str1,只能往str1的后面添加字符变成str2,要求str2
整体都是回文串且最短。

2.2 思路

1、只要求出包含了str1中最后一个字符的最长回文子串s即可。

2、然后将子串s在str1中之前的字符逆序添加到str1末尾即可。

如“abc12321”,包含“1”的最长回文子串是“12321”,将“abc”逆序添加到“abc12321”末尾,变成“abc12321cba”,即为所求的str2。

2.3 代码

#include <iostream>#include <string>#include <vector>using namespace std;/*2017/11/18Manacher算法扩展:给定一个字符串str1,只能往str1的后面添加字符变成str2,要求str2整体都是回文串且最短。*/#if 1#define max(a,b)(a>b?a:b)#define min(a,b)(a<b?a:b)string newString(string s){    string news = "#";    for (int i = 0; i < s.length(); i++)        news = news + s[i] + "#";    return news;}//都用奇回文解决int Manacher(string s){    int C = -1;    int R = -1;    string news = newString(s);    vector<int>radius(news.length());    for (int i = 0; i < news.size();i++)    {        radius[i] = R>i ? min(radius[2 * C - i], R - i) : 1;        while (i-radius[i]>=0 && i+radius[i]<news.length())        {            if (news[i - radius[i]] == news[i + radius[i]])                radius[i]++;            else                break;        }        if (i + radius[i] > R)        {            R = i + radius[i];            C = i;            if (R == news.length())                return radius[i] - 1;        }    }}string ShortestEnd(string s){    int r = Manacher(s);    string s_short = s;    for (int i = s.length() - r - 1; i >= 0; i--)        s_short += s[i];    return s_short;}void main(){    string s1 = "abc1234321";    cout << ShortestEnd(s1) << endl;    string s2 = "abb3";    cout << ShortestEnd(s2) << endl;    system("pause");}#else#endif

2.4 结果截图

这里写图片描述