前端分享会--从一道算法题目开始的学习

算法题

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

理解

所谓回文,就是字符串反过来或者顺着念都是一样的。这就意味着我们可以通过字符串与反向排序之后的字符串来比较,如果相等,显然就是回文了。

那么在一个字符串中,如果找到最长的那个回文呢?

我们很容易想到的办法就是从字符串连续二个字母开始,然后取出连续的三个字母的子字符串判断有没有回文,紧接着三个,....,一直到长度小于s1个长度的子串来判断是否是回文。
当2个连续字母的子串,我们要判断 n-1次,
当3个连续字母子串,我们判断n-2次,
...
当n-1个连续字母子串,我们判断2次。
然后我们可以把字符串转成数组,然后倒转在转成字符串来比较是否相同,复杂度是1(很多文章,是通过前后字母来对比判断是否为回文,这种方式的复杂度是n)
加起来时间复杂度为n^2
这种方法是最能够想到的最简单的方法,有没有时间复杂度更低点的呢?

我们能否在比较n个连续的子字符串时,通过前面的已经比较过的字符串的是否回文来推倒出来呢?要做到这点,我们首先要保存已经比较过的字符串的是否回文,很显然通过以上方法,我们很难保存已经比较过的子字符串是否是回文的标识,因为子字符串太多,也不一样,不好去保存,标识唯一。要么需要很多空间去保存。

我们知道回文有个特点,第1位字母和最后一个字母相同,第二个字母和倒数第二个字母一样,以此类推。我们能否通过以字母为中心的唯一标识来判断,以该中心的子字符串最长的回文呢?是不是方便多了。

以每个字符为中心,向两边扩展,我们只需要一个数组arr[n]来保存每一位的最长回文子字符串。我们就叫它中心扩展法吧。

中心扩展法还要处理一个问题。既然是中心向两边扩展,像aaaa这种偶数长度回文如何判断呢?
我们在字符串插了特殊字符即可a#a#a#a,这样我们可以通过以特殊字符为中心扩展来判断是否是回文。达到奇偶统一。

由此我们在使用中心扩展方法是,如果单纯来遍历再判断是否是回文,复杂度n,通过不断向外扩展比较求得最长回文时间复杂度是n 。所以总体n^2。时间复杂度没有明显的降下来。

既然我们保存之前的字母为中心的最长回文。我们如何利用呢。我们假设 s=cabadabae,则S=c#a#b#a#d#a#b#a#e ,现在我们已经知道S[8]之前的最长回文arr=[0,0,3,0,7,0,15],并且是 #a#b#a#d#a#b#a#现在我们如何推导S[8]以后的最长回文呢?



因为我们知道S[7]的最长回文是0,S[8]最长回文是15,回文最后的位置在S[15],我们就知道S[9]的最长回文就是0。

以同样的道理因为S[6]的最长回文长度只有3,S[5]最长回文长度是0。推出,S[10]最长回文是3,S[11]最长回文是0。

那S[12]的最长回文是多少呢?


image.png

我们发现,我们不能够确定arr[12]===arr[4],因为我们S[12]的最长回文编辑搞好和S[8]最长回文的最后字母重合S[16],我们必须知道S[16]并且和S[8]进行对比,才能知道S[12]的最长回文是不是超过7。

我们通过以上的陈述,总结出以下规律:
1、针对奇偶回文的问题,我们加入特殊字符#,达到统一方便比较。
2、在推导下一个字母为中心的回文时,我们可以先判断这个字母是否已经越过了之前记录的最长回文的最长处,如果超过了,我们要从该字母为中心扩展,一步一步去判断它的最长回文。
如果没有超过,我们根据对称点遭到对称字母k,通过k的最长回文长度,得知当前字母的回文长度,如果这个回文长度最后字母在之前记录的最长回文的最长处内,我们可以认为它是最长,如果不是,那么我们以此继续扩展,一步一步去判断它的最长回文。

通过上面的规律,我们大概用js来写下代码:

var longestPalindrome = function(s) {
    if(!s) return "";
    if(s.length===1)return s;
    if(s.length>1000) return s
    var p='#'
    for(var k=0;k<s.length;k++){
        p=p+s.charAt(k)+'#'
    }
    console.log(p)
    var R=0;
    var c=0;
    var arr=new Array()
    arr[0]=1
    var max={len:1,index:0}
   for(var i=1;i<p.length;i++){
     if(i>=R){
         arr[i]=1
     }else{
         arr[i]=Math.min(arr[2*c-i],R-i)
     }
       while(p[i-arr[i]]&&p[i+arr[i]]&&p[i-arr[i]]===p[i+arr[i]])arr[i]++
       
       if((i+arr[i]-1)>R){
           R=i+arr[i]-1
           c=i
       }
       max.index=arr[i]>max.len?i:max.index
       max.len=arr[i]>max.len?arr[i]:max.len
   }
    return p.substr(max.index-max.len+1,2*max.len-1).split('#').join('')
};

动态规划的出现

在上面的题目中,我们发现通过前面来推导后面的最长回文,这种方式叫动态规划方式。
下一节,我们来学习动态规划的经典算法题

推荐阅读更多精彩内容