扩展kmp求最长回文子串_算法-字符串之最长回文子串

扩展kmp求最长回文子串_算法-字符串之最长回文子串上一篇KMP算法之后好几天都没有更新,今天介绍最长回文子串。首先介绍一下什么叫回文串,就是正着读和倒着读的字符顺序都是一样的,eg:level,noon。而回文子串,顾名思义,就是主串中满足回文性质的子串。求解的常规思想,就是先求出主串的所有子串,在判断是否是回文串,然后选出最长的,这一种方法的时候复杂度较高,是O(n^3),所以一般不采用这种方法,下面介绍两种方法求解。1.中心扩展法中心扩展法…

大家好,又见面了,我是你们的朋友全栈君。

上一篇KMP算法之后好几天都没有更新,今天介绍最长回文子串。

首先介绍一下什么叫回文串,就是正着读和倒着读的字符顺序都是一样的,eg:level,noon。而回文子串,顾名思义,就是主串中满足回文性质的子串。

求解的常规思想,就是先求出主串的所有子串,在判断是否是回文串,然后选出最长的,这一种方法的时候复杂度较高,是O(n^3),所以一般不采用这种方法,下面介绍两种方法求解。

1. 中心扩展法

中心扩展法可以说是常规算法的改进。首先我们知道,回文串是中心对称的,相比从头到尾遍历字符串的方法,从中间开始向两边扩展,时间会减少一半。

算法思想:把主串中的每一个字符当做回文串的中心,向两边扩展,求出最长的回文子串。其中要注意奇数位的回文子串和偶数位的回文子串的区别。eg:aba的中心是b,而abba的中心应该是bb。使用中心扩展法的时间复杂度是O(n^2),空间复杂度是O(1)。

代码

核心算法是l2r的部分,以传入的mid为回文串的中心计算最长的回文子串,其中需要注意的地方有两点:

l2r中的第一个while循环,之前提到过要注意奇数位的回文串和偶数位的回文串,在代码中,判断中心点的字符和右边的字符是否相等,就可以跳过偶数位的中心点。

注意l2r中返回语句,从第二个while循环中跳出的时候,已经多进行了一步left–,right++的操作了。

int longest;//子串长

int start;//最长回文子串在主串中的起始位置

/*计算以mid为中心的最长回文子串*/

int l2r(char *string, int mid) {

int len = strlen(string);

int left = mid – 1, right = mid + 1;

//跳过相同的部分

while (string[right] == string[mid])

right++;

while (left > 0 && right < len){

if (string[left] == string[right]) {

//从中间开始算,各向外移一位

start = left;

left–;

right++;

}

else {

break;

}

}

//重置回文串的起始位置

if (left < 0)

start = 0;

//printf(“\tstart:%d left:%d right:%d len:%d\n”, start, left, right, right – 1 – (left + 1) + 1);

//跳出while循环的时候,要么是index不满足条件,要么是当前right和left的位置字符不等,都要在将right,left还原成删一个状态才能计算回文串长

return right – 1 – (left + 1) + 1;

}

/*计算最长回文子串的长度*/

int longestPald(char *string) {

int len = strlen(string);

if (string == NULL || len == 0)

assert(“ERROR”);

if (len == 1){

longest = 1;

start = 0;

return 1;

}

for (int i = 0; i < len; i++){//遍历整个主串,以它的每一位为回文串的中心,计算最长的回文串

int tempLen = l2r(string, i);

if (tempLen > longest) {

longest = tempLen;

}

}

return 0;

}

int main(void)

{

char string[] = “abcdeedcbdac”;

longestPald(string);

printf(“\t%s的最长回文子串:\n”, string);

printf(“\t起始位置:%d 串长:%d\n”, start, longest);

system(“pause”);

return 0;

}

结果:

94c71898bdc4

abcdeedcbdac的最长回文子串:bcdeedcb

2. 动态规划法???

之前看到网上有很多用动态规划法求解最长回文子串的,但是我看了之后觉得有问题。动态规划法中是用二维矩阵保存回文串长,c[i][j]表示主串中s[i…j]是回文串,当前位置的c[i][j]需要依赖于c[i+1][j-1],但是有的地方c[i+1][j-1]是不知道的,反而觉得用递归来计算矩阵c会更好。不知道是我理解错误还是这个方法确实不对。如果有用动态规划法求解出最长回文子串的,还请赐教~

3. Manacher算法

这是几个方法中最为高效的方法,时间复杂度为O(n).Manacher算法也是利用回文串的对称性,标记回文串的中间位,向两边遍历。同样是标记中间位,向两边遍历,那它和中心扩展法有什么区别呢?

区别:中心扩展法的思想是以主串的每一个字符为中心,计算最长的回文子串,外层循环执行n次,内存循环至多2/n次;而Manacher的中心字符并不是这样的,Manacher利用之前计算过的回文子串,巧妙的计算出新的中心点。但同时它也做出了一些折中的处理,比如说,要确定唯一的中心点,所以要扩展主串。

算法思想:Manacher采用从中间向两边遍历得到最长回文子串的思想,将原来的主串进行扩展,这个算法严格要求对称,只允许有一个中心点。eg:abc– > #a#b#c#,这样不管回文串是奇数位还是偶数位都都会变成奇数位的,满足只有一个中心字符的要求。Manacher利用之前计算的回文子串,避免了一些重复的回文子串的计算。

辅助变量:

既然要利用之前求得信息,就需要记录。

p[]:数组p保存的是主串中以某个字符为中心的最长回文子串的半径,eg:p[i]存储的是以str[i]为中心的最长回文串的半径,这个半径值是在扩展之后的字符串中。

mid:保存得到的回文串的中心点。

max:保存当前的回文串的影响范围,也就是这个回文串的右边界。

注:mid和max的值是由最长回文串计算得到的。

现在,我们来看一下str和p的关系,便于理解。s是在原来的字符串

94c71898bdc4

s和p的关系

接下来计算p[],这时要用到max和mid。先解释一下最难懂的地方。利用之前计算的回文子串的信息计算当前的p[i],现则最小的值。

p[i] = (max – i) > p[j] ? p[j] : (max-i);

解释:(以下解释摘自另一篇博客)

1.当 mx – i > P[j] 的时候,以S[j]为中心的回文子串包含在以S[id]为中心的回文子串中,由于 i 和 j 对称,以S[i]为中心的回文子串必然包含在以S[id]为中心的回文子串中,所以必有 P[i] = P[j],见下图。

94c71898bdc4

当 mx – i > P[j] 的时候

2.当 P[j] > mx – i 的时候,以S[j]为中心的回文子串不完全包含于以S[id]为中心的回文子串中,但是基于对称性可知,下图中两个绿框所包围的部分是相同的,也就是说以S[i]为中心的回文子串,其向右至少会扩张到mx的位置,也就是说 P[i] >= mx – i。至于mx之后的部分是否对称,就只能一个一个匹配了。

94c71898bdc4

P[j] > mx – i 的时候

接下来解释算法为线性的原因:(算法中其实有两层循环)

94c71898bdc4

image.png

代码:

代码中有几个需要注意的地方:

在pre函数中,扩展主串时,扩展串的第一个位置是’$’,这是为了诸侯方便处理越界的问题。而字符串越界会出现在哪里呢?就是manacher中的为一个一个while循环那里。

注意重置longest和start时候的值,在介绍str和p的关系的时候已经提到过p[i]-1的意义,在设置longest和start时要考虑到这个关系。(longest是最长回文子串的长度,start是其在原串中的下标)。

理解p[i] = (max – i) > p[j] ? p[j] : (max-i);很重要,结合代码中的注释和上面的图多理解。

int pre(char *string, char *strCopy) {

strCopy[0] = ‘$’;

int j = 1;

for (int i = 0; i < strlen(string); i++) {

strCopy[j++] = ‘#’;

strCopy[j++] = string[i];

}

strCopy[j] = ‘#’;

return j + 1;//表示strCopy的长度

}

void manacher(char *str,int n) {

int p[MAXLEN];//数组p中保存字符串str中以某一点为中心点的最长回文子串的半径

p[0] = 0;//p[0]对应str[0]–>$

//max存储之前计算的回文子串的右边界,mid保存当前的回文子串的中心,这两个值都不一定是最长回文子串求得

int max = 0, mid = 1;

for (int i = 1; i < n; i++) {

if (max > i)

{

int j = 2 * mid – i;//j是字符串中i关于mid的对称点

p[i] = (max – i) > p[j] ? p[j] : (max – i);//!!!

}

else {//否则max

//那么就不能用便捷方法来计算p[i],只能一个一个计算

p[i] = 1;//初始值为1

}

//基于当前以i为中心的回文串的半径,计算下一个位置的字符是否满足回文。这里会出现越界的问题!!!

while (str[i – p[i]] == str[i + p[i]]){

p[i]++;

}

if ((i + p[i]) > max){

max = i + p[i];//当前计算得到的回文串已经大于之前计算的边界了,重置边界

mid = i;

}

if (longest < p[i] – 1){//p[i]-1就是原串中以i为中心的回文串的长度

longest = p[i] – 1;

//在遇到最长回文子串包含第0个字符的时候,start计算得-1,所以这里要处理一下

if ((i – p[i] – 1) < 0)

start = 0;

else

start = i – p[i] – 1;

}

}

}

int main(void)

{

char string[] = “acab”;//”abcdeedcbdac”;

char strCopy[MAXLEN];

int len = pre(string, strCopy);

printf(“原串:%s –>”, string);

//输出strCopy

for (int i = 0; i < len; i++){

printf(“%c”, strCopy[i]);

}

printf(“\n”);

manacher(strCopy,len);

printf(“\t%s的最长回文子串:\n”, string);

printf(“\t起始位置:%d 串长:%d\n”, start, longest);

system(“pause”);

return 0;

}

结果:

94c71898bdc4

manacher算法的结果

总结

好了,这次就到这里了。不足之处,欢迎指正。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/137926.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Java打印数组的四种方式「建议收藏」

    Java打印数组的四种方式「建议收藏」1.使用循环结构这里使用的是for循环publicclassPrintArrays{ publicstaticvoidmain(String[]args){ int[]a={1,2,3,4,5}; for(inti=0;i<a.length;i++){ System.out.print(a[i]+”\t”); } }}…

    2022年5月25日
    40
  • kali不能使用arpspoof命令_kali不能使用arpspoof命令_ARP欺骗工具arpspoof的用法「建议收藏」

    kali不能使用arpspoof命令_kali不能使用arpspoof命令_ARP欺骗工具arpspoof的用法「建议收藏」arpspoof是一个好用的ARP欺骗工具,Kalilinux中自带了该工具,在ubuntu中,安装它只需运行命令:sudoapt-getinstalldsniff安装完成后,输入命令:manarpspoof可以查看使用手册,2.4版本的手册内容如下(自己翻译的,非官方):名字arpspoof-截获交换局域网中的数据包用法arpspoof[-iinterface][-cow…

    2022年10月6日
    4
  • idea在mac版怎么配置svn_IntelliJ Idea 集成svn 和使用

    idea在mac版怎么配置svn_IntelliJ Idea 集成svn 和使用最近公司的很多同事开始使用IntelliJIdea,便尝试了一下,虽然快捷键与eclipse有些不同,但是强大的搜索功能与“漂亮的界面”(个人认为没有eclipse好看),还是值得我们去使用的。刚开始使用的idea要去集成svn,下载公司的项目。我是用的是TortoiseSVN(小乌龟),下载后安装,然后记住安装路径,我安装的是64位的。TortoiseSVN的下载地址:htt…

    2022年10月17日
    5
  • Mysql命令_MySQL alter

    Mysql命令_MySQL alter基于Mysql5.7版本的explain参数详解…Mysql官网相关参数解读一:idSELECT标识符1.id越大越先执行2.相同id,从从往下执行二:select_type1.SIMPLE:最简单的查询(没有关联查询没有子查询没有union的查询语句)2:PRIMARY:子查询最外层的查询语句3.SUBQUERY:子查询内层查询语句4.DERIVED:派生表查询,FROM后的不是表而是查询后的结果集5.UNION:union或unionall中的第二个以后的查询表6.U

    2025年12月3日
    3
  • BeanCopier_contabo测评

    BeanCopier_contabo测评概述常见或常用的几种Bean属性复制工具Apache.BeanUtilsApache.PropertyUtilSpring.BeanUtilsCglib.BeanCopierMapStructEZMorph使用场景:Dto与Entity转换普通属性复制个别属性过滤属性类型转换数组或集合拷贝性能对比测试在两个简单的Bean之间转换的耗时,执行次数分别为10、10…

    2025年9月14日
    8
  • 手机上有哪些不错的c语言编程软件?[通俗易懂]

    手机上有哪些不错的c语言编程软件?[通俗易懂]手机上编程C语言的软件其实非常多,下面我介绍2个不错的软件,分别是C语言编译器和C++编译器,这2个软件都可以在手机上直接编译运行C语言程序,而且使用起来非常不错,下面我简单介绍一下这2个软件的安装和使用:C语言编译器1.首先,下载安装C语言编译器,这个可以直接到手机应用商店中搜索,如下,大概也就13兆左右:2.安装完成后,打卡这个软件,就可以直接新建C语言文件,进…

    2025年9月6日
    5

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号