5. Longest Palindromic Substring

解法

记一下一题多解,求最长回文子串

解法一:最长公共子串

如果想用SS[::-1]的最长公共子串来求最长回文子串,会有一个问题就是:当S=abcdfcbaS[::-1]=abcfdcba时,最长的公共子串是abc,它显然不是回文。
解决方法是在dp的时候判断一下公共子串的下标是否对应
但是这个方法效率不高,反正我超时了

class Solution(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        t = s[::-1]
        n = len(s)
        dp = [0]*n
        ans = ""
        for i in xrange(n):
            dp[i] = dp[i-1]
            for j in xrange(i,-1,-1):
                l = 0
                while i>=l and j>=l and s[i-l]==t[j-l]:
                    l += 1
                if dp[i]<l:
                    if i+j-l+1==n-1:
                        dp[i] = l
                        ans = s[i-l+1:i+1]
                l = 0
                while i>=l and j>=l and t[i-l]==s[j-l]:
                    l += 1
                if dp[i]<l:
                    if i+j-l+1==n-1:
                        dp[i] = l
                        ans = t[i-l+1:i+1]
        return ans

解法二:暴力

就是枚举每对(i,j)对,判断是不是回文

解法三:DP

相当于是有记忆的暴力,(i,j)是不是回文取决于(i+1,j-1)是不是,并且s[i]是不是等于s[j]

class Solution(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        n = len(s)
        dp = [[False]*n for _ in xrange(n)]
        ans = ""
        for i in xrange(n):
            dp[i][i] = True
            if len(ans)<len(s[i]):
                ans = s[i]
        for l in xrange(1,n):
            for i in xrange(n-l):
                dp[i][i+l] = (2>l or dp[i+1][i+l-1]) and s[i]==s[i+l]
                if dp[i][i+l]:
                    ans = s[i:i+l+1]
        return ans

解法四:扩展

之前想到过这种,每个回文串的“种子”是所有长度为1的串所有长度为2且相同的串,然后不断向两边扩展

class Solution(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        n = len(s)
        cand = set()
        ans = ""
        for i in xrange(n):
            cand.add((i,i))
            if i<n-1 and s[i]==s[i+1]:
                cand.add((i,i+1))
        while cand:
            nextc = set()
            for i,j in cand:
                if len(ans)<=j-i:
                    ans = s[i:j+1]
                if i>0 and j<n-1 and s[i-1]==s[j+1]:
                    nextc.add((i-1,j+1))
            cand = nextc
        return ans

解法五:Manacher算法

参考:https://www.felix021.com/blog/read.php?2040

  1. 首先把所有的回文子串统一为奇数长度,具体如何做呢?把字符之间加上分隔符#,如bob加上后变#b#o#b#长为奇数,moom变成#m#o#o#m#长度为奇数,所以问题就变成找长度最长的奇数的字符串了
  2. 奇数的字符串可以由一个二元组表示(i,r),其中i为字符串中心的坐标,r为从中心延伸出去的长度
  3. 接下来就是算法的精髓了:
    假设rm为右边界在最右边的回文子串的中心,p[rm]为它的半径,我们现在遍历到中心i,当然i大于rm,并且小于i的所有中心的半径都已经知道了
    我们可以求出i关于rm的对称点j=2*rm-i,它在rm左边并且有着准确的半径p[j]
    • 如果i<=rm+p[rm],那么irm的回文子串内,同时j也在。

      1. 如果p[j]<=rm+p[rm]-i,那么j的回文子串整个都在rm的回文子串里,如下图,由对称性可知,p[i]=p[j]
        1
      2. 如果p[j]>rm+p[rm]-i,那么j的回文子串有一部分在rm的回文子串里,有一部分在rm的回文子串外,至少在rm的回文子串里的那部分是相同的,在rm外的就只能自己遍历确定了。如果新的边界大于rm的边界,还需要更新。
        2
    • 如果i>rm+p[rm],那么也只能遍历来确定了,并且这个时候一定需要更新rm

class Solution(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        n = len(s)
        n = 2*n+1
        p = [0]*n
        rm = 0
        
        def same(i,j):
            if i%2==0 and j%2==0:
                return True
            if i%2!=0 and j%2!=0 and s[i//2]==s[j//2]:
                return True
            return False
        
        ans = ""
        
        for i in xrange(1,n):
            if i<=rm+p[rm]:
                j = 2*rm-i
                p[i] = min(p[j], rm+p[rm]-i)
                while i+p[i]<n-1 and i-p[i]>0 and same(i+p[i]+1, i-p[i]-1):
                    p[i]+=1
            else:
                while i+p[i]<n-1 and i-p[i]>0 and same(i+p[i]+1, i-p[i]-1):
                    p[i]+=1
            if i+p[i]>rm+p[rm]:
                rm = i
            b = (i-p[i])//2
            e = (i+p[i]+1)//2
            if len(ans)<e-b:
                ans = s[b:e]
        return ans

速度快了一个数量级= =

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值