笔试面试算法经典–最长回文子串

笔试面试算法经典–最长回文子串回文的定义正读和反读都相同的字符序列为“回文”,如“abba”、“abccba”是“回文”,“abcde”和“ababab”则不是“回文”。字符串的最长回文子串,是指一个字符串中包含的最长的回文子串。例如“1212134”的最长回文子串是“12121”。下面给出了三种求最长子串的方法。解法1(中心扩展法)时间复杂度O(n^2),空间复杂度为O(1)。中心扩展法的思路是,遍历到数组的某一个元素时,以这

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

回文的定义

正读和反读都相同的字符序列为“回文”,如“abba”、“abccba”是“回文”,“abcde”和“ababab”则不是“回文”。字符串的最长回文子串,是指一个字符串中包含的最长的回文子串。例如“1212134”的最长回文子串是“12121”。下面给出了三种求最长子串的方法。

解法1(中心扩展法)

时间复杂度O(n^2),空间复杂度为O(1)。中心扩展法的思路是,遍历到数组的某一个元素时,以这个元素为中心,向两边进行扩展,如果两边的元素相同则继续扩展,否则停止扩展。如下图:当遍历到3时

这里写图片描述

但是中心扩展法有一个问题,如下图:

这里写图片描述

1,2,2,1是一个回文串,然而找不到对称中心,这样以一个元素为中心向两边扩展就不好用了,这里有一种比较方便的处理方式,就是对1,2,2,1进行填充,比如说用#进行填充如下:

这里写图片描述

如下图这样填充之后就又成了一个中心对称的回文串,因此对于一个串进行处理时,先进行扩充,这样可以使中心扩展时比较方便,不用对非中心对称的子串进行特殊处理。假设填充前的子串长度为 len 那么扩充或的长度为:2 * len+1,由这个关系也可以很方便的得到扩充前回文串的长度。
代码

public void palindrome(String str)
    {
        if(str==null||str.length()==0)
            return ;
            //如果str为null 或长度为0直接返回。
        StringBuffer sb=new StringBuffer(str);
        for(int i=0;i<str.length();i++)
        {
            sb.append("#");
            sb.append(str.charAt(i));           
        }
        //对给出的串进行填充。
        sb.append("#"); 

        char chs[]=sb.toString().toCharArray();
        int max_len=0;
        for(int i=0;i<chs.length;i++)
        {
            //遍历到位置i时,对i进行中心扩展
            max_len=Math.max(subpalidromelen(chs,i), max_len);
            //subpalidromelen(chs,i),以i为中心进行中心扩展,max_len 保存最长回文子串的长度。 
        }
        System.out.println(max_len);
    }
//中心扩展函数 
public int subpalidromelen(char []chs,int i)
    {

        int len=0;

        for(int k=0;k<=i&&k<(chs.length-i);k++)
        {
            if(chs[i-k]==chs[i+k])
            {
                len++;

            }
            else {
                break;
            }
        }

        return len-1;
        //因为是填充之后的串,填充之前的回文子串值为len-1。
    }       

上面代码做两点说明:
①palindrome()对给出的字符串填充后进行遍历,对遍历的每一个元素调用中心扩展函数获得回文子串的值,并保存最长回文子串的值。

②subpalidromelen()中心扩展函数,返回回文子串的长度。

解法2(动态规划)

时间复杂度O(n^2),空间复杂度O(n^2),如果用f[i][j] 保存子串从i 到j是否是回文子串,那么在求f[i][j] 的时候如果j-i>=2时,如果 f[i][j] 为回文,那么f[i+1][j-1],也一定为回文。否则f[i][j]不为回文。如下图:

这里写图片描述

因此可得到动态规划的递归方程:

这里写图片描述
代码:


public void palindrome1(String str)
    {

        if(str==null||str.length()==0)
            return ;
        char chs[]=str.toCharArray();
        int max_len=0;
        boolean f[i][j]=new boolean[chs.length][chs.length];
        for(int j=0;j<str.length();j++)
        {
            int i=0;
            f[j][j]=true;
            //一个元素肯定是回文串。
            for(;i<j;i++)
            {
                f[i][j]=(chs[j]==chs[i]&&(j-i<2||j>0&&f[i+1][j-1]));
                //如果chs[j]==chs[i]当串的长度小于等于2时,肯定是回文子串,如 1,1,就是回文串。
                //如果长度大于2时,则需要判断f[i+1][j-1]是不是回文。
                if(f[i][j])
                {                   
                    max_len=Math.max(max_len, j-i+1);
                }
                //max_len保存最大回文子串的长度。 
            }                       
        }
        System.out.println(max_len);
    }

两点说明:

①当子串的长度为1时肯定为回文子串,对应上面的 f[j][j] = true 。当子串的长度为 2 且 里面的两个元素相同时,则也是回文子串。对应上面的 f[i][j]= chs[i]&&(j-i<2).

②当串的长度大于2时,如串为121时,要判断chs[j]==chs[i]&&f[i+1][j-1]),依赖子串。

Manacher

时间复杂度O(n),空间复杂度O(n)观察上面的中心扩展法,发现遍历到每一个元素的时候都要进行一次中心扩展,完全没有利用前面回文子串的信息,Manacher算法的核心思想,就是利用前面遍历的时候产生的回文子串,如下图:

这里写图片描述

上图中已经求出了红色部分3的回文子串,现在如何求3右边的1,2,1的回文子串呢,利用回文子串的对称性,如下图:

这里写图片描述
①2*id-i,id , i表示的是数组的下标,2*id-i 与 i 关于 id对称,如果用p[i],表示i位置的最长回文子串,则在上图的这种情况下p[i]=p[2*id-i],由于 p[2*id-i] 在3的左边已经求出来了,因此p[i]很快就可以得到,然而当要求右边2的最长回文的时候如下图:

这里写图片描述

②发现右边2的回文子串并不等于左边2的回文子串,而且比左边的要长,还有下面的这种情况:
这里写图片描述
③左边2的回文怎么比右边2的回文长。

综合上面三种情况:其实可以的到下面的公式

这里写图片描述

由于manacher算法与上面中心扩展法有一样的问题,所以先向字符串中插入#,如下图:

这里写图片描述
在上面的基础上:
引入一个辅助变量MaxRight,表示当前访问到的所有回文子串,所能触及的最右一个字符的位置。另外还要记录下MaxRight对应的回文串的对称轴所在的位置,记为pos,它们的位置关系如下。
这里写图片描述
我们从左往右地访问字符串来求RL,假设当前访问到的位置为i,即要求RL[i],在对应上图,i必然是在po右边的(obviously)。但我们更关注的是,i是在MaxRight的左边还是右边。我们分情况来讨论。

1)当i在MaxRight的左边

情况1)可以用下图来刻画:
这里写图片描述
我们知道,图中两个红色块之间(包括红色块)的串是回文的;并且以i为对称轴的回文串,是与红色块间的回文串有所重叠的。我们找到i关于pos的对称位置j,这个j对应的RL[j]我们是已经算过的。根据回文串的对称性,以i为对称轴的回文串和以j为对称轴的回文串,有一部分是相同的。这里又有两种细分的情况。

以j为对称轴的回文串比较短,短到像下图这样。
这里写图片描述
这时我们知道RL[i]至少不会小于RL[j],并且已经知道了部分的以i为中心的回文串,于是可以令RL[i]=RL[j]。但是以i为对称轴的回文串可能实际上更长,因此我们试着以i为对称轴,继续往左右两边扩展,直到左右两边字符不同,或者到达边界。

以j为对称轴的回文串很长,这么长:
这里写图片描述

遇到这种情况,说明以i为对称轴的回文串还没有任何一个部分被访问过,于是只能从i的左右两边开始尝试扩展了,当左右两边字符不同,或者到达字符串边界时停止。然后更新MaxRight和pos。


代码:

public void manacher(String str)
    {
        if(str==null||str.length()==0)
        {
            return;
        }
        int len=str.length();
        StringBuffer sb=new StringBuffer(str);
        for(int i=0;i<len;i++)
        {
            sb.append("#");
            sb.append(str.charAt(i));           
        }
        sb.append("#"); 
        //先往串中插入#后不用再区分两种情况了。
        int id=0,i=0,mx=0;
        int n=sb.length();
        int p[]=new int[n];
        int max_len=0;
        char chs[]=sb.toString().toCharArray();
        for(i=1;i<n;i++)
        {
            if(mx>i)
                p[i]=Math.min(p[2*id-i], mx-i);
                //如果i在最大回文子串的中间,就可以利用上面的分析p[i]表示i的最长回文子串的长度
            else {
                p[i]=1;
                //否则p[i]=1,
            }
            for(;(i-p[i]>=0&&i+p[i]<n)&&(chs[i-p[i]]==chs[i+p[i]]);p[i]++)
            ;
            //在上面的基础上进行扩展。
            if(i+p[i]>mx)
            {
                mx=p[i]+i;
                id=i;
            }
            //如果i+p[i]>mx,就更新mx。
            max_len=Math.max(max_len, p[i]-1);
        }   
        System.out.println(max_len);
    }   

参考文献:https://segmentfault.com/a/1190000003914228

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

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

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


相关推荐

  • 人力资源管理系统详细设计说明书[通俗易懂]

    人力资源管理系统详细设计说明书[通俗易懂]人力资源管理系统详细设计说明书组名 : K2组员 : 罗猛、丘佩茵2021年1月12日目录1引言 31.1编写目的 31.2背景说明 31.3定义 31.4参考资料 32系统结构 42.1总系统结构图 42.2管理员登录注册模块结构图 42.3部门管理模块结构图 52.4员工管理模块结构图 52.5培训管理模块结构图 62.6招聘管理模块结构图 62.7奖惩管理模块结构图 72.8薪资管理模块结构图 72.9系统管理模块结构图 81.10查看消息模块结构图 83系

    2022年5月15日
    42
  • OSPF协议总结(1)

    OSPF协议总结(1)

    2021年4月14日
    183
  • 关于HTTP请求出现 405状态码 not allowed的解决办法

    关于HTTP请求出现 405状态码 not allowed的解决办法

    2021年10月12日
    58
  • 200行Html5+CSS3+JS代码实现动态圣诞树

    200行Html5+CSS3+JS代码实现动态圣诞树一、前言最近CSDN的热榜出现了很多用Python、C/C++等编程语言实现的圣诞树,这篇文章用前端三大杀手Html5、CSS、Js来实现动态圣诞树二、

    2022年7月25日
    14
  • 流行计算机病毒有哪些,现在流行计算机病毒有哪些[通俗易懂]

    流行计算机病毒有哪些,现在流行计算机病毒有哪些[通俗易懂]现在流行计算机病毒有哪些现在流行计算机病毒有哪些现在流行的计算机病毒有很多!你有去了解过吗?下面由小编给你做出详细的现在流行计算机病毒介绍!希望对你有帮助!现在流行计算机病毒介绍一:国家计算机病毒应急处理中心通过对互联网的监测发现,近期出现一种恶意后门程序变种Backdoor_Agent.ADG。该变种运行后,会自我复制到受感染操作系统指定文件夹下,重命名为可执行文件。随后,该变种会释放操作系统中…

    2022年5月5日
    66
  • 如何理解掩码、反掩码、通配符「建议收藏」

    如何理解掩码、反掩码、通配符「建议收藏」反掩码、掩码和通配符的区别一、掩码在掩码中,1表示精确匹配,0表示随机1和0,永远不交叉;1永远在左边,0永远在右边;在配置IP地址以及路由的时候,会使用掩码;二、反掩码在反掩码中,1表示随机,0表示精确匹配0和1,永远不交叉;0永远在左边,1永远在右边;在路由协议的配置中,通过network命令进行网段宣告时,会使用三、通配符在统配符中,1表示随机,0表示精确匹配0和1的位置,没有任何的固定限制可以连续,可以交叉在ACL中,使用的通配符通配符掩码表CIDR子网掩码反掩

    2022年7月19日
    17

发表回复

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

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