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


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/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

2.4 结果截图

这里写图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值