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一共分了四种情况讨论。
当遍历到i时,如果i在R的右边,则只能采取暴力匹配方式,直接以其为中心查找回文。
当遍历到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/18
Manacher算法:
找出字符串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/18
Manacher算法扩展:
给定一个字符串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