Manacher算法

Manacher算法使用场景

Manacher算法用于解决求一个字符串的最长回文子串长度问题,且时间复杂度为O(N)。

一般的求一个字符串的最长回文子串问题的方法

举个栗子:如下图
在这里插入图片描述
对于字符串str,将str重写为str’的形式,即给每个字符前后加‘#’(不一定为#,也可以为其他字符)。然后使用下标指针,从str‘[0]开始遍历,将每个字符往两边“扩”,若两边相等则len+1,“扩”至不等,则将红色箭头后移。以此类推。
以上图为例,显然str’[0]时不能扩,因此len=0,红色箭头后移;str’[1]时只能扩一次,因此len=1,红色箭头后移;依次类推,直到str’[9]时能向两边3次,故len=3。显然以后的len都没有str’[9]的了len大,因此最长回文子串为232。

Manacher算法详解

显然,经典算法的时间复杂度可以高达O(N^2),因此我们使用Manacher算法来优化经典算法,使时间复杂度达到O(N)。
在了解Manacher算法以前,先了解几个概念

回文半径,回文直径,回文半径数组,之前扩的所有位置汇总所到达的回文的有边界R,最远边界的中心点C

举个栗子来说明这几个概念:如图
在这里插入图片描述
则str的:
最长回文半径为:4
最长回文直径为:7
之前扩的所有位置汇总所到达的回文的有边界R:每个字符的最长回文所到达的最右边的下标
最远边界的中心点C:每个字符的最长回文的中心所在的下标
最长回文半径数组为:{-1,0,2,2,4,4,8,8,8,8,8,8}
如下图:
在这里插入图片描述

Manacher算法

<1> 首先按照一般的回文的方法,将字符串转化为带‘#‘的形式
<2> 然后依次遍历每个字符,按照一般方法向外扩,扩不了就往下遍历。在扩的过程中可以得到每个字符的R和C;
<3> 若遍历到的当前位置,没有在最右边的最长回文右边界中,则使用<2>的方法暴力扩
<4> 若遍历到的当前位置i,在最右边的最长回文右边界中,找到i与R对应的C的对称位置i’,如图所示:
在这里插入图片描述
分为下面三种情况进行分析:
case 1 :
i’的回文域完全在L,R中,如下图:
在这里插入图片描述
因为i与i’的回文半径是一样的,所以i位置不用扩也知道没有c位置的回文半径大,因此直接就跳过了

case2:
i‘的回文域部分在L,R中,如下图:
在这里插入图片描述
此时,i的回文半径为i位置到R的距离,因此i位置也不用扩了,因为没有c位置的回文半径大。

case 3:
i’的回文域的边界恰好落在c位置回文半径的界上,如图:
在这里插入图片描述
此时i的回文半径也刚好到边界R,因此直接跳到R+1的位置重复<3><4>过程。

Manacher算法C++代码实现

#include<iostream>
#include<string>
#include<math.h>
using namespace std;
inline int max(int a, int b){ return a > b ? a : b; }
inline int min(int a, int b){ return a > b ? b : a; }
char* changeStr(string str)//该函数间str转为带#的形式
{
 char*s = (char*)str.c_str();
 char*re = new char[str.length() * 2 + 1];
 int index = 0;
 for (int i = 0; i < str.length() * 2 + 1; ++i)
 {
  if (i % 2)
  {
   //若i为奇数
   re[i] = s[index++];
  }
  else
  {
   re[i] = '#';
  }
 }
 return re;
}
int max_LS_length(string str)//得到最长回文子串的长度
{
 if (str.length() == 0)return 0;//若字符长度W为0 直接返回0
 char *s = changeStr(str);//转化字符形式
 int len = str.length() * 2 + 1;
 int *arr = new int[len];//回文半径数组
 int C = -1;//回文中心
 int R = -1;//最大回文右边界+1的位置,即最大回文右边界为R-1
 int Max = INT_MIN;//最大值
 for (int i = 0; i < len; ++i)//遍历每个位置
 {
  //所有不用验的i位置的回文半径:
   //当i>=R时,说明i在最大回文右边界(R-1)外,则不用验的回文半径为1,也就是i位置的字符本身
   //当R>i时,说明i在最大回文右边界(R-1)内,则不用验的回文半径为R-i与arr[i']=arr[2*C-i]之中最小的那个。(这个就是case1,case2,case3中不用扩的部分)
  arr[i] = R>i ? min(arr[2 * C - i], R - i) : 1;
  //接下来,从不用验的部分往后走,然后扩
  while (i + arr[i]<len&&i-arr[i]>-1)
  {
   if (s[i + arr[i]] == s[i - arr[i]])
    arr[i]++;//若满足回文则往外扩
   else 
    break;//否则跳出不扩了
  }
  if (i + arr[i] > R)
  {
   //若当前位置加上当前位置的回文半径大于R,说明最大回文半径右边界+1(R)要跟新,同时C也要跟新为i
   R = i + arr[i];
   C = i;
  }
  Max = max(Max, arr[i]);//跟新max
 }
 //释放堆空间
 if (s != NULL)
  delete[]s;
 s = NULL;
 if (arr != NULL)
  delete[]arr;
 arr = NULL;
 return  Max-1;//因为处理串(带#的串)回文半径的长度,与原始回文串(不带#的串)的长度关系就是就是max-1的关系
}
int main(void)
{
 string str1 = "abc1234321ab";
 cout << max_LS_length(str1);
 system("pause");
 return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值