5. 最长回文子串:manacher算法

回文串算法的本质其实就是遍历中心,然后不断左右扩!manacher算法就是遍历到一些中心的时候不需要重新左右扩,因为manacher算法就是排除掉了不可能是最长回文子串的中心,还有以一种巧妙的方式一下子就得出当前回文中心点的暂时左右边界,虽然不是最左和最右,但不需要从中心点开始扩了。

首先对字符串来的间隙填充 “ababbaba” 变成 “#a#b#a#b#b#a#b#a#”。

然后定义几个名词
1、回文中心点,其实就是遍历字符串的每一个点(包括填充字符)都要作为回文中心点。
2、某回文串的最右边界
3、目前为止的所有回文串的最右边界(注意和2不同)
3、回文半径(包括了填充符号),当前回文中心点的到该字符串最左(右)边界的字符数(包括填充字符)。
4、回文半径数组.(记录字符串每个字符为回文中心点时的回文半径)
5、回文直径,回文串长度

算法步骤:填充,然后遍历填充字符
每遍历一个字符,将其是作为当前回文中心点。
两种情况,一种是当前的回文中心点i在上一个回文中心点a的最右边界之外。…a…i…
这样就比对 i+k和i-k就可以,k=0,1,2…

一种是当前回文中心点i在a最右边界之内 …a…i…
(为什么是最右边界?因为是从左到右遍历的呀)
第一种情况就暴力扩就是了 …a…i…
第二种情况: …a…i…
i’…a…i
这时,i的回文半径可以借鉴其对称点i‘(关于a点对称),我们已经求出了i’的回文半径了!
所以这里就分成了三种情况,
一种时i‘的回文半径在a的左边界内,
一种是i‘的回文半径在a的左边界上。
一种是i’的回文半径超出了a的左边界。
先看第一种小情况:
b…i’.a.i…c i的半径和i’的回文半径一样,因为a的回文串性质使然。

第二种,
b__i’__cac__i__c , 由于i’的回文左边界在a的左边界上,所以i和i’只能保证在a内相同。外面的还需要对比一下。
对比从 i + (i’的回文半径) 和 i - (i’的回文半径) 开始

第三种,
fdc__i’__cdeaedc__i__b,这个是i‘的回文左边界在a的左边界之外了。所以只能参考a的边界以内的,i’在a边界之后的回文不可以参考,所以需要自己比对。好像第二种和第三章一样。
对比从i + (5)和i - (a的左边界点到i‘的距离) 开始
a的左边界点到i‘的距离 = i‘坐标 - a的左边界
a的左边界点 = a-a的回文半径
a的左边界点到i‘的距离 = i‘坐标 - a + a的回文半径

class Solution:
    def longestPalindrome(self, s: str) -> str:
        # 先填充s
        new_s = []
        i = 0
        while i < len(s):
            new_s.append("#")
            new_s.append(s[i])
            i += 1
        new_s += ["#"]
        s = new_s

        # 定义当前所有回文子串最右边界的回文半径,
        R = -1   # 初始化为-1
        # 定义最右边界对应的中心点坐标
        a = -1 # 初始化为-1
        # a + R - 1为当前所有回文子串最右边界
        # a - R + 1为对应该最右边界的回文子串的左边界

        # 定义回文半径数组
        lenList = [-1] * len(s)

        # print(s)
        
        temp_max = 1
        temp_max_i = 0
        for i in range(len(s)):
            
            # 当前中心在边界之外
            if i > a + R - 1:
                
                # r_i为当前i点的半径
                r_i = 0
                # k为向左右做的位移
                k = 0
                while s[i-k] == s[i+k]:
                    k += 1
                    r_i += 1 # 刚开始是i点自己和自己比,半径为0+1=1
                    if i-k < 0 or i+k >= len(s):
                        break
                lenList[i] = r_i
                R = r_i
                a = i

            # i落在了R_i为中心的回文子串范围内
            else: 
                # 算出对称点i'的坐标
                i_ = a - (i-a) 
                # 算出i'的回文左边界
                left_i_ = i_ - lenList[i_] + 1
                # 三种情况之i的对称点i'的回文左边界在a的左边界内
                if left_i_ > a - R + 1:
                    lenList[i] = lenList[i_]
                # 三种情况之i的对称点i'的回文左边界在a的左边界上
                elif left_i_ == a - R + 1:
                    r_i = lenList[i_]
                    k = lenList[i_]
                    
                    while i+k < len(s) and s[i-k] == s[i+k]:
                        k += 1
                        r_i += 1 # 刚开始是i点自己和自己比,半径为0+1=1

                    lenList[i] = r_i
                    R = r_i
                    a = i                  
                # 三种情况之i的对称点i'的回文左边界在a的左边界之外
                else:
                    k = i_ - a + R
                    r_i = i_ - a + R
                    while  i+k < len(s) and s[i-k] == s[i+k]:
                        k += 1
                        r_i += 1 # 刚开始是i点自己和自己比,半径为0+1=1

                    lenList[i] = r_i
                    R = r_i
                    a = i          
            # print("R: ",lenList[i])
            if lenList[i] > temp_max:
                temp_max = lenList[i]
                temp_max_i = i
        res = s[temp_max_i-temp_max+1: temp_max_i+temp_max]
        res_str = ""
        for i in res:
            if i != "#":
                res_str += i
        return res_str   
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值