Longest Palindromic Substring 最长回文串 python实现

一、  题目:

输入字符串s,输出该字符串中包含的最长回文串。回文串,指的是正着看和倒着看都一样的字符串,例如’abdba’。

二、几种解法

1、      Brute-force解法

(1)、思路:

第一步,通过两层for循环得到输入字符串s的所有可能子串。

第二步,逐个判断子串是否为回文串。若当前子串为回文串且长度大于之前得到的回文串,更新当前最长回文串。

(2)、代码

'''
Brute-force解法
'''
class Solution1(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        lens=len(s)
        if lens<2:
            return s
        maxlen=0
        start=0
        for i in range(lens):
            for j in range(i+1,lens):
                begin=i
                end=j
                while begin<end:
                    if s[begin]!=s[end]:
                        break
                    begin+=1
                    end-=1
                if begin>=end and j-i>maxlen:
                    maxlen=j-i+1
                    start=i
        if maxlen>0:
            return s[start:maxlen]
        return None

(3)、复杂度分析:

时间复杂度:

得到字符串所有子串的时间复杂度为O(n^2),判断子串是否为回文串的时间复杂度为O(n),二者相乘得到暴力解法的时间复杂度为O(n^3).

空间复杂度:

该方法没有使用额外空间,空间复杂度为O(n)

2、      动态规划方法

(1)、思路:

子问题:

      以i开始,以j结束的子串的最长回文串

状态:

      令P[i,j]表示:以i开始,以j结束的子串是否为回文串。其值为0,1。P[i,j]==0表示子串s[i:j]不是回文串,P[i,j]==1表示子串s[i:j]是回文串。

 

状态转移方程:

   

 

终止条件:

      遍历完所有子串

(2)、代码

'''
动态规划解法:
dp数组:维护子串状态
step 1:初始化dp数组,完成长度小于3的子串状态判断
step 2:i为子串长度,j为子串起始地址,r为子串结束地址.
第二步逐步得到长度为i的子串状态,利用状态转移方程完成这一判断。
step 3:根据第二步得到的最长子串长度和起始位置,得到最终结果
'''
class Solution2(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        if not s:
            return None
        lens=len(s)
        if lens<2:
            return s
        maxlen=0
        start=0
        dp=[[0]*lens]*lens

        #step 1
        for i in range(lens):
            dp[i][i]=True
            if i<lens-1 and s[i]==s[i+1]:
                dp[i][i+1]=1
                start=i
                maxlen=2

        #step 2
        for i in range(3,lens+1):
            for j in range(0,lens-i+1):
                r=j+i-1
                if dp[j+1][r-1] and s[j]==s[r]:
                    dp[j][r]=1
                    maxlen=i
                    start=j
        #step 3
        if maxlen>=2:
            return s[start:start+maxlen]
        return None

(3)、算法复杂度分析:

时间复杂度:

得到字符串所有子串的时间复杂度为O(n^2),判断子串是否为回文串的时间复杂度为O(1),二者相乘得到动态规划法的时间复杂度为O(n^2).

空间复杂度:

该方法需要额外的空间维护dp数组,空间复杂度为O(n^2)。

3、      中心扩展法

(1)、思路:

step 1:遍历每个字符,把每个字符当做中心逐步向两边扩展,每扩展一步就得到一个新的子串。这里针对输入字符串的长度,扩展方式需要根据长度奇偶性质做判断。

Step 2:判断子串是否为回文串,更新当前最长回文串

Step 3:返回最长回文串

(2)、代码:

'''
中心扩展法
step 1:遍历每个字符,把每个字符当做中心逐步向两边扩展,每扩展一步就得到一个新的子串。
这里针对输入字符串的长度,扩展方式需要根据长度奇偶性质做判断。
Step 2:判断子串是否为回文串,更新当前最长回文串
Step 3:返回最长回文串
'''
class Solution3(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        lens=len(s)
        maxlen=0
        start=0

        # 长度为奇数
        for i in range(lens):
            j = i - 1
            k = i + 1
            while j >= 0 and k < lens and s[j] == s[k]:
                if k - j + 1 > maxlen:
                    maxlen = k - j + 1
                    start = j
                j -= 1
                k += 1

        # 长度为偶数
        for i in range(lens):
            j = i
            k = i + 1
            while j >= 0 and k < lens and s[j] == s[k]:
                if k - j + 1 > maxlen:
                    maxlen = k - j + 1
                    start = j
                j -= 1
                k += 1

        if maxlen>0:
            return s[start:start+maxlen]
        return None

(3)、算法复杂度分析:

时间复杂度:

遍历字符串的时间复杂度为O(n),中心扩展及判断子串是否为回文串的时间复杂度为O(n),二者相乘得到动态规划法的时间复杂度为O(n^2).

空间复杂度:

该方法没有使用额外的空间,空间复杂度为O(n)。

4、      manacher算法

(1)、思路:

算法主要解决两个问题:

问题一:长度奇偶性带来的对称轴位置问题

       解决办法就是字符串内插入特殊字符'#',处理后字符串长度为奇数。

问题二:重复访问的问题

解决办法是计算字符i回文半径时尽量利用之前回文串匹配的结果,减少重复字符比对。

利用上次匹配结果部分,涉及到的变量有:存储字符i回文半径的数组P,上一个回文串的中心位置c以及回文串结束位置r。

如果本次字符位置i小于上一个回文串结束位置r,那么上一个回文串与以i为中心的回文串有重复部分,重复部分的利用需要参考当前字符关于中心c的对称位置i’,同时需要考虑不要超出上个回文串的结束位置。如果没有重复部分可以利用,那么不断中心扩展。

(2)、代码:

'''
Manacher算法:
step 1: 字符串内插入特殊字符'#',处理后字符串长度为奇数;字符串收尾插入特殊字符,避免数组越界
step 2:逐个遍历字符,计算得到以每个字符为中心的最长回文串半径。
涉及到的变量有:存储字符i回文半径的数组P,上一个回文串的中心位置c以及回文串结束位置r。
计算字符i回文半径:本次计算尽量利用之前回文串匹配的结果,减少重复字符比对。
'''
class Solution4(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        if not s:
            return None
        if len(s)<2:
            return s
        T='#'.join('@{}$'.format(s))#step 1
        #step2
        n=len(T)
        P=[0]*n
        c=0
        r=0
        for i in range(1,n-1):
            i_mirror=c-(i-c)#i关于中心c的对称位置
            if r>i:#利用之前回文串字符对比重复部分
                P[i]=min(r-i,P[i_mirror])
            # 中心扩展法完成之前没有涉及的字符比对
            while T[i+1+P[i]]==T[i-1-P[i]]:
                P[i]=P[i]+1
            #更新当前回文串中心c及终止位置r
            if i+P[i]>r:
                c=i
                r=i+P[i]
        #找到最大回文半径及对应的回文中心
        maxlen=0
        centeridx=0
        for i in range(1,n-1):
            if P[i]>maxlen:
                maxlen=P[i]
                centeridx=i
        #获取最长回文串
        begin=(centeridx-maxlen)//2
        end=(centeridx+maxlen)//2
        return s[begin:end]

(3)、算法复杂度分析:

时间复杂度:

      遍历字符串的时间复杂度为O(n), 只对尚未匹配的部分进行中心扩展的时间复杂度为O(1),二者相乘得到动态规划法的时间复杂度为O(n).

空间复杂度:

      该方法没有使用额外的空间,空间复杂度为O(n)。

 

参考博客:

[1] https://articles.leetcode.com/longest-palindromic-substring-part-ii/

[2] https://segmentfault.com/a/1190000003914228

[3] http://blog.csdn.net/kangroger/article/details/37742639








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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值