• --算法恩仇录-01-小虾米回忆马拉车--(Manacher回文串算法)


    是日,小虾米于门中无事,闲来下山走走。路经一处山脚,此山巍峨耸立,远远望去山尖似乎有一高塔。小虾米心中好奇,不禁询问上山之人:“兄台,敢问此为何处?”,那位无面之人轻笑答道:“此山乃算法山脉一脉,名为阿狗丽泽山(algorithm),此山最高峰名为阿狗丽泽峰,至于那座宝塔,自然是闻名于江湖的力扣塔(LeetCode)!”

    闻名江湖?!

    小虾米暗暗心惊,此有名之物我怎么不知道。一时也顾不得闲游,忙追上先前之人的步伐,继续询问有关力扣塔一事。

    原来,这宝塔中闻名于世,不知其高,无人知晓其层数几何。但其宝塔中,每层都关卡重重,江湖中人,任何人都可前去闯关,可从任意一层开始,每层难易未知,但难以挑战的关卡也有高手破关过,简单关卡也有江湖新手通过。于是江湖中就流传出力扣榜,记录每位闯关者闯过大大小小的关卡,挑战次数,战胜几何,闯关数排名均有记录。

    如此宝地,小虾米岂能不前去一看?

    少几,小虾米来到塔前,有一层名为“最短回文串”。

    咦?小虾米心中一疑,不禁回想起当前在修武堂所学... ...

    Manacher算法

    俗称‘马拉车’,用于处理回文字符串的一种针对字符串使用的算法。

    1.什么是回文串?

      回文串,是一类字符串。这类字符串 从左至右的字符排列 和 从右至左的字符排列 一致。

      例如:‘abcba’,就是一个回文串。

      

      马拉车,则是解决找寻一个正常字符串中的最大回文串的长度。

      如果我们使用暴力枚举,对每一个字符进行 “同时向该字符两边进行遍历匹配操作的话”,其时间复杂度为O(n^2)。(同时还要考虑 字符串的长度奇偶 的问题,偶数长度字符串与奇数长度字符串的遍历起始点取值稍有不同)

      而马拉车这能将时间复杂度完成为O(n)。

      如何实现?请往下看。

    2.马拉车的实现

      1.处理原字符串

        对于原字符串,我们首先需要对其进行一个处理。对于每个字符中间需要加上非匹配的字符。在字符串首尾添加另一类字符。

        如:  原字符串:‘aaaba’

            处理后:‘$a#a#a#b#a#*’

        其意义在于,将原字符串 偶数长度、奇数长度的字符串 全部转换为 奇数字符串 进行处理。

      2.思路

        开始讲解马拉车的思路之前,先考虑下一个问题,这对算法的理解有很大帮助。

        问题:如果要优化暴力枚举遍历字符串的步骤使其复杂度降下来,应该如何优化?或者说应该在哪一步进行优化?

        暴力枚举的解决办法为:

          遍历每个点,再由每个点向两端遍历。

        那么在这过程中会产生一个问题,例如字符串:‘abcdcba’ 中,我们先遍历了'd' 字符,然后再遍历了第二个'c'字符,可我们已经知道了'd'的最大回文长度为7,知道了d是左右对称的回文串,总长度长度为7,那么我们还要重新遍历'c'的回文长度,岂不是进行了没必要的操作?

        ‘c’是已经在‘d’的遍历中判断过了,依据‘d’的长度还可以知道,在‘d’的左边也有一个‘c’,那么我们完全可以将第一个‘c’的状态赋给第二个‘c’的状态,省去对第二个‘c’的遍历。

        马拉车的优化就在于此。

        对于每一个字符,都会将其位置i与之前字符的最大回文进行一个比较。

        

        (假设当前遍历到i字符串,将i之前遍历到的最大回文中心点cpoint的最远距离记为rpoint)

        这时候就有两种状态:

        1.i < rpoint ,即当前点i处于之前遍历过的最大回文中,属于已经遍历过的部分。

          那么此时,我们需要找到i以cpoint为轴进行对称的点j。(j<i,j点由于已经遍历过,因此j点的回文状态是已知的),因此我们可以借由j的状态得到i的状态。

          但此时又会出现一个问题:如果j点的回文长度会延伸到cpoint的回文长度之外,我们如果直接获取j的状态,会将未匹配过的字符交付给i。

          因此还需要一个len[j]与rpoint-i (最长回文已匹配的长度)的比较。取其最小值即可。

        2.i > rpoint, 即当前点i处于之前还未遍历到的位置。

          此时,需要自己重新进行遍历,为后续点进行更新状态。

        这就是马拉车的核心部分。

        因此,我们需要一个存储状态的数组len,以及记录最大回文中心点cpoint和最大回文最远点rpoint。

        模板代码如下:(JS编写)

        

     1 console.log('Code Running ...');
     2 console.log('This is Manacher Algorithm...')
     3 const initString = function(str0){
     4     str = '$#';
     5     
     6     for(let i = 0; i<str0.length; i++){
     7         str += str0[i];
     8         str += '#';
     9     }
    10     // str += '*';
    11     return str;
    12 }
    13 const getLen = function(str){
    14     str = initString(str);
    15     let len=[];    //初始化len数组
    16     let rpoint=0, cpoint=-1;//rpoint为最右端的点,cpoint为回文串的中心点
    17     for(let i=1; i<=str.length; i++){
    18         //当前点是否在已经匹配过的回文串中
    19         //是,则取 i关于cpoint对称点的len值 和 rpoint与i的差值
    20         if(rpoint > i){   
    21             len[i] = Math.min(len[2*cpoint - i], rpoint - i);
    22         } else {
    23             //否则直接为1
    24             len[i] = 1;
    25         }
    26         //遍历回文
    27         //如果为1,说明len还未匹配这么远,重新对该点遍历
    28         for(;str[i - len[i]] == str[i + len[i]];len[i]++){}
    29         if(len[i] + i > rpoint) rpoint = len[i] + i,cpoint = i;
    30     }
    31     //返回得到的len数组
    32     return len;
    33 }
    34 let str0 = 'aaaba';
    35 const len = getLen(str0);
    36 console.log(len);

        PS:代码部分主要是对len数组的求解,如果需要获取最大长度,自行遍历即可~

        

    离大侠再近一步!
  • 相关阅读:
    能量石
    Journey among Railway Stations —— 1J
    金明的预算方案
    css学习
    实验七
    计算圆的面积和窗体的跳转
    Android子菜单和选项菜单与上下文菜单的实现
    Android 对话框(Dialog)
    /etc/init.d 与 service 的 关系 。。。。。。。
    Linux && Aix 下 常用命令 dd
  • 原文地址:https://www.cnblogs.com/Samo-Li/p/13583503.html
Copyright © 2020-2023  润新知