Leetcode-P5 Longest Palindromic Substring 最长回文子串的一个线性解法 Manacher‘s Algorithm

博客介绍了如何使用Manacher's Algorithm解决LeetCode第5题——最长回文子串问题。算法通过插入特殊字符确保所有字符在奇数位置,简化比较过程,利用已知回文串的性质来动态更新回文子串的最大长度。文章还提供了直观的解释和示例来帮助理解算法的工作原理。
摘要由CSDN通过智能技术生成

失踪人口再次回归,最近闲来无事准备重新刷一下算法题,一来锻炼一下脑子,二来看看这几年学的算法分析类课程还记得多少,准备从LeetCode第一题开始,期间记录一下自己觉得有意思或者是不会的题目

第五题是求一个字符串的最长回文子串,我一开始的想法是DP,用一个二维数组dp[i][j]来标记从下标i到j是否是一个回文串。然后对于每一种可能的i,j 对,根据状态转移方程 dp(i, j) = dp(i+1, j-1) and s[i+1] == s[j-1] 来决定值。之后寻找最长值为true的i, j对就可以。但是很明显这个时间复杂度是O(n^2)。 然后看了一下题解,发现了一个线性算法

我理解的这个算法思想是这样的
首先他通过在每个字符之间插入一个#,并在开头和结尾分别放入^和$从而保证了每个字母都是处于奇数位置,从而将原题目中可能出现的奇偶两种可能统一到了一种算法中。

其中原来的奇数回文串的中心还是原来的字母,而偶数的中心则成为了#.

同时这样也简化了比较的过程,只需要每次都是比较两端是否相等,并统计长度,最后的长度即为原始字符串中对应的回文串长度。

这个过程有个很生动形象的网站http://manacher-viz.s3-website-us-east-1.amazonaws.com/#/

在以上基础上,这个算法利用了一个事实:假设有个已知的回文串s,从下标L开始,到R结束,回文串的中心点在C。那么C左右两侧的字符串是完全相等的(在回文串的意义上)。也就是说如果L-C之间有个子回文串,那么对应的在C-R的位置上一定有个一样长度的镜像回文串。

利用这个本质,我们对于一个位置的点i。则有2种可能,i<R, i>=R.

i<R:

对于这种情况,又有两种可能,可能性A:首先是他关于C镜像位置上的回文串长度 + i <R。代表在这个回文串在这个大的回文串内已经结束了,那么i的值,一定是等于镜像位置上的值。

可能性B:另一种可能是i关于C镜像位置上的回文串长度 + i >= R。那么我们不确定R右侧是否还有相同的字母,那么我们需要继续寻找直到不相等之后

以刚才的网站上的字符串为例:

  对于情况A:i = 10, 镜像位置是6.

因为镜像位置上的值是1 + 10 < R = 15的,所以说明这个是确定10位置的值是镜像上的值1.因为我们已经遍历过6号位置的回文串,知道长度1之后就是不一样的字母了,因为镜像相等,所以10号位置的回文串最长就是1.

但是情况B:i=12, 镜像位置是4

但是,因为镜像位置的值是3,12+3 = 15 已经位于右侧的边界了。所以我们无法保证R之后的位置是否还能构成回文串,因为以C为中心的回文串并没有判断这一部分,所以我们要在镜像的值3的基础上继续向右侧遍历。直到遇到不一样的。同时B情况之后要以i为新的中心来做后面的判断

而对于另一种可能就是i不在L-R的范围内,那么说明i周围的信息是未知的,我们要从i开始挨个判断,思路跟B情况基本一致。这个也要更新新的中心点C。

这样走完一次之后,就将所有的回文串找出来了,然后就判断一下最长的返回就行了。

代码:

class Solution:
    def longestPalindrome(self, s: str) -> str:
        # intert a symbol to guarantee get a odd length string
        oddS = "^"
        for i in s:
            oddS += "#"
            oddS += i
        oddS += "#$"
        
        c = 0   # known palindromic center index
        r = 0   # right range
        i = 1   # current undetermind palindromic center. Start from first "#" symbol
        ans = [0]*len(oddS)
        maxAns = 0
        ansCenter = -1
        
        while i < len(oddS)-1:
            # there are three possible situation
            # First is current index i is on or out the range of r
            if i >= r:
                l = i - 1
                r = i + 1
                counter = 0
                # since the start and end denoted by special character.
                # When traverse to start or end it will must stop
                # thus we do not need to condsider the boundary
                while oddS[l] == oddS[r]:
                    l -= 1
                    r += 1
                    counter += 1
                    
                c = i
                ans[c] = counter
                r -= 1  # set the r boundary to the last same character
            
            else:
                # if current index i in the range
                # since current index in a bigger palindromic string which centered at c
                # we can ensure that the mirror index mi of i about c has same pattern
                # then there comes two sub-situation
                # one is the mirror index mi's length + index i is still in center's range r
                # that means ans[mi] is the answer of i
                if i + ans[c-(i-c)] < r:
                    ans[i] = ans[c*2-i]
                else:
                    # else means i possible have more palidromic character out the range r
                    # we need to traverse out the range r
                    counter = r - i     # that because we could not guarantee out boundary r 
                                        # the ramain part still same as inside of palindrome
                    
                    l = i*2 -r -1
                    r = r + 1
                    
                    while oddS[l] == oddS[r]:
                        l -= 1
                        r += 1
                        counter += 1
                    # and update the new center
                    c = i
                    ans[c] = counter
                    r -= 1

            if maxAns < ans[i]:
                ansCenter = i
                maxAns = ans[i]
            i += 1
            
            
        ansS = ""
        l = ansCenter - maxAns
        r = ansCenter + maxAns
        while l <= r:
            if not oddS[l] == '#':
                ansS += oddS[l]
            l += 1
                
        return ansS
            
            
        

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值