最长回文字符串

简介:

回文串就是一个正读和反读都一样的字符串,比如“level”或者“noon”等等就是回文串。回文子串,顾名思义,即字符串中满足回文性质的子串。比如输入字符串 "google”,由于该字符串里最长的对称子字符串是 "goog”,因此输出4。

1.问题解决的基本方法

分析:可能很多人都写过判断一个字符串是不是对称的函数,这个题目可以看成是该函数的加强版。 
要判断一个字符串是不是对称的,不是一件很难的事情。我们可以先得到字符串首尾两个字符,判断是不是相等。如果不相等,那该字符串肯定不是对称的。否则我们接着判断里面的两个字符是不是相等,以此类推。

复制代码
复制代码

#include<iostream>
using namespace std;
//字符串是否对称
bool isAym(char *cbegin, char *cend)
{
    if(cbegin == NULL || cend ==NULL || cbegin > cend)
    {
        return false;
    }
    while(cbegin<cend)
    {
        if(*cbegin!=*cend)
        {
            return false;
        }
        cbegin++;
        cend--;
    }
    return true;
}
复制代码
复制代码

现在我们试着来得到对称子字符串的最大长度。最直观的做法就是得到输入字符串的所有子字符串,并逐个判断是不是对称的。如果一个子字符串是对称的,我们就得到它的长度,最后经过比较,就能得到最长的对称子字符串的长度了。

复制代码
复制代码
//O(n*n*n)复杂度的子字符串
int getMaxSym(char * str)
{
    if(str == NULL)
        return 0;
    int maxlength = 0, strlength = 0;
    char *pFirst = str;
    char *strEnd = str + strlen(str);
    while(pFirst < strEnd)
    {
        char *pLast = strEnd;
        while(pLast > pFirst)
        {
            if(isAym(pFirst, pLast))
            {
                strlength = pLast - pFirst + 1;
                if(strlength > maxlength)
                {
                    maxlength = strlength;
                }
            }
            pLast --;
        }
        pFirst ++;
    }
    return maxlength;
}
复制代码
复制代码

上述方法的时间效率:由于需要两重while循环,每重循环需要O(n)的时间。另外,我们在循环中调用了IsSym,每次调用也需要O(n)的时间。因此整个函数的时间效率是O(n^3)。 
假设输入:abcddcba,按照上述程序,要分割成 'abcddcba’, 'bcddcb’, 'cddc’, 'dd’…等字符串,并对这些字符串分别进行判断。不难发现,很多短子字符串在长些的子字符串中比较过,这导致了大量的冗余判断,根本原因是:对字符串对称的判断是由外向里进行的。 
换一种思路,从里向外来判断。也就是先判断子字符串(如dd)是不是对称的。如果它(dd)不是对称的,那么向该子字符串两端各延长一个字符得到的字符串肯定不是对称的。如果它(dd)对称,那么只需要判断它(dd)两端延长的一个字符是不是相等的,如果相等,则延长后的字符串是对称的。

2.改进的解决方案

根据从里向外比较的思路写出如下代码:

复制代码
复制代码
//改进后的程序
int getMaxSym2(char * str)
{
    if(str == NULL)
        return 0;
    int maxlength = 0;
    char *ptag = str;
    while(*ptag !='\0')
    {
        //奇数子字符串
         char *left = ptag - 1;
        char *right = ptag + 1;
        int oddlenght = 1;
        while(left >= str && *right != '\0' && *left == *right)
        {
            left--;
            right++;
            oddlenght += 2;
        }
        if(oddlenght > maxlength)
        {
            maxlength = oddlenght;
        }
        //偶数子字符串
         left = ptag;
        right = ptag + 1;
        int evenlength = 0;
        while(left >= str && *right != '\0' && *left == *right)
        {
            left--;
            right++;
            evenlength += 2;
        }
        if(evenlength > maxlength)
        {
            maxlength = evenlength;
        }

        ptag++;
    }
    return maxlength;
}
复制代码
复制代码

由于子字符串的长度可能是奇数也可能是偶数。长度是奇数的字符串是从只有一个字符的中心向两端延长出来,而长度为偶数的字符串是从一个有两个字符的中心向两端延长出来。因此程序中要把这两种情况都考虑进去。 
由于总共有O(n)个字符,每个字符可能延长O(n)次,每次延长时只需要O(1)就能判断出是不是对称的,因此整个函数的时间效率是O(n^2)。 
上述方法称为朴素算法,关于字符串的题目常用的算法有KMP、后缀数组、AC自动机,这道题目利用扩展KMP可以解答,其时间复杂度也很快O(N*logN)。但是,这里介绍一个专门针对回文子串的算法,其时间复杂度为O(n),这就是manacher算法。

3.manacher算法

算法的基本思路是这样的:把原串每个字符中间用一个串中没出现过的字符分隔#开来(统一奇偶),同时为了防止越界,在字符串的首部也加入一个特殊符$,但是与分隔符不同。同时字符串的末尾也加入'\0'。算法的核心:用辅助数组p记录以每个字符为核心的最长回文字符串半径。也就是p[i]记录了以str[i]为中心的最长回文字符串半径。p[i]最小为1,此时回文字符串就是字符串本身。 
示例:原字符串 'abba’,处理后的新串 ' $#a#b#b#a#\0’,得到对应的辅助数组p=[0,1,1,2,1,2,5,2,2,1]。 
程序如下,对应的变量解释在后面

复制代码
复制代码
//预处理,将str:abba转换为: $#a#b#b#a#\0(从1开始)
char * pre(char *str)
{
    int length = strlen(str);
    char *prestr = new char[2*length + 4];
    prestr[1] = '$';
    for(int i=0;i<length;i++)
    {
        prestr[2*(i+1)] = '#';
        prestr[2*(i+1)+1] = str[i];
    }
    prestr[2*length+2]='#';
    prestr[2*length+3]='\0';
    return prestr;
}
复制代码
复制代码

以下是manacher算法的具体实现,包括:辅助数组的构建、最大字符串长度的获取。

复制代码
复制代码
//manacher算法
int getMaxSym3(char *str)
{
    char *prestr = pre(str);
    int mx =0, pi=1;//边界和对称中心
    int len = strlen(prestr);
    //辅助数组
    int *p = new int[len];
    p[0] = 0;
    for(int i=1;i<len;i++)
    {
        if(mx>i)
        {
            p[i]=min(mx-i,p[2*pi-i]);//核心
        }
        else
        {
            p[i]=1;
        }
        while(prestr[i-p[i]]==prestr[i+p[i]]&&i-p[i]>0&&i+p[i]<len)
        {
            p[i]++;
        }
        if(i+p[i] > mx)
        {
            mx = p[i] + i;
            pi = i;
        }
    }
    //最大回文字符串长度
    int maxlen = 0;
    for(int i=0;i<len;i++)
    {
        if(p[i]>maxlen)
        {
            maxlen = p[i];
        }
    }
    delete []prestr;
    delete []p;
    return maxlen - 1;
}
复制代码
复制代码

上面几个变量说明:pi记录具有遍历过程中最长半径的回文字符串中心字符串。mx记录了具有最长回文字符串的右边界。 
image 
pi是最长回文字符串(淡蓝色)的中心,如果以j为中心的最大回文串如上如所示,那么i处的情况与j处相同(关于pi的两侧是对称的)。这样便减少了运算量,i的对称位置是2*pi-i。 
但是有另外一种情况,就是j的一部分超出蓝色部分,这时p[i]=p[j]就不一定对了,如下图 
image 
这就为什么有取最小值这个操作:

if(mx>i)
{    
    p[i]=min(mx-i,p[2*pi-i]);//核心
}

剩下的代码就很容易看懂了。

最后遍历一边p数组,找出最大的p[i]-1就是所求的最长回文字符串长度,说明如下:
(1)因为p[i]记录插入分隔符之后的回文字符串半径,所以以i为中心的回文字符串长度为2*p[i]-1。例如:bb=>#b#b#,中间#的半径为3,回文字符串长度为2*3-1; 
(2)注意上面两个串的关系。 #b#b#减去一个#号的长度就是原来的2倍。即((2*p[i]-1)-1)/2 = p[i]-1,得证。

来源:http://www.cnblogs.com/houkai/p/3371807.html





本文转自夏雪冬日博客园博客,原文链接:http://www.cnblogs.com/heyonggang/p/3386724.html,如需转载请自行联系原作者

目录
相关文章
|
消息中间件 设计模式 Java
聊聊 Kafka: Consumer 源码解析之 Rebalance 机制
聊聊 Kafka: Consumer 源码解析之 Rebalance 机制
875 0
|
前端开发 开发者 UED
React 18 与之前版本的主要区别
【10月更文挑战第12天】 总的来说,React 18 的这些区别体现了 React 团队对于提升应用性能、用户体验和开发效率的持续努力。开发者需要适应这些变化,充分利用新特性来构建更出色的应用。同时,随着技术的不断发展,React 也将继续演进,为开发者带来更多的创新和便利。
594 58
|
JavaScript API PHP
不用SMTP实现联系表单提交后发送邮件到指定邮箱
构建网站时,联系表单可通过邮件API(如SendGrid、Mailgun、Amazon SES)或第三方自动化服务(Zapier、Integromat)无需SMTP发送邮件。这些服务提供API接口和自动化工作流程,简化邮件发送。例如,使用SendGrid API在Python中发送邮件涉及注册、获取API密钥并编写发送邮件的代码。同样,Zapier可作为表单提交的触发器,自动发送邮件。此外,后端脚本(如PHPMailer)也能实现这一功能,但需编写处理SMTP的代码。选择适合的方法能优化邮件发送流程。
|
前端开发 JavaScript Java
Docker 极简入门教程,傻瓜都能看懂!
Docker 极简入门教程,傻瓜都能看懂!
662 0
Docker 极简入门教程,傻瓜都能看懂!
|
前端开发 JavaScript 开发者
React的useId,现在Vue3.5终于也有了!
【9月更文挑战第22天】React 的 `useId` 功能已在 Vue 3.5 中引入,用于生成唯一 ID,提升开发效率,确保组件 ID 的一致性和增强应用的可访问性。开发者能更简便地管理唯一标识符,减少繁琐工作,同时保证在服务器和客户端渲染下的稳定性。这一改进使得 Vue 应用更加高效、易用和可靠。
171 3
|
IDE Java 开发工具
灵活配置 Spring 集合:List、Set、Map、Properties 详解
使用<property>标签的value属性配置原始数据类型和ref属性配置对象引用的方式来定义Bean配置文件。这两种情况都涉及将单一值传递给Bean
454 1
|
调度
TM4C123库函数学习(2)--- LED闪烁,滴答定时器精准延时
TM4C123库函数学习(2)--- LED闪烁,滴答定时器精准延时
555 0
|
SQL 关系型数据库 MySQL
MySQL大无语事件:一次生产环境的死锁事故,看看我怎么排查
今天要分享的是在生产环境中出现的一次算得上比较诡异的死锁事件, 不过庆幸的是没有产生较大的业务损失.
阿里巴巴开源的15个顶级Java项目
上个周末抽时间整理了一些阿里开源的一些 Java 开源项目,希望对大家有帮助!这篇文章收录的所有开源项目都是还在继续维护并且可以使用的。 虽然有部分项目不是那么”完美“,但是依然非常值得我们学习。 感谢阿里技术团队的小伙伴们为 Java 开源生态做的贡献!