最长回文子串动态规划_LeetCode5最长回文子串

本文探讨了多种求解最长回文子串的有效算法,包括暴力法、动态规划及中心扩展算法。通过实例演示每种方法的特点及适用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ceba861e6e2df56d19d0414b41c4e099.png

这两天被这个题弄得是有些崩溃了,因为之前试的两种方法都是超时了,所以弄得后面都有些不想弄了。还好有度娘,最后用的是从中间往两边扩展的方法得此解决,真的是如释重负啊!废话不说,讲正文贴代码。

题目如下:

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为1000。

示例 1:

输入: "babad"

输出: "bab"

注意: "aba"也是一个有效答案。

示例 2:

输入: "cbbd"

输出: "bb"

方法一:暴力法

有些人会忍不住提出一个快速的解决方案,不幸的是,这个解决方案有缺陷(但是可以很容易地纠正):

反转 SS,使之变成 S'S′。找到 SS 和 S'S′ 之间最长的公共子串,这也必然是最长的回文子串。

这似乎是可行的,让我们看看下面的一些例子。

例如,S = {“caba”}S=“caba” , S' = {“abac”}S′=“abac”:

SS 以及 S'S′ 之间的最长公共子串为{“aba”}“aba”,恰恰是答案。

让我们尝试一下这个例子:S = {“abacdfgdcaba”} S=“abacdfgdcaba” , S' = {“abacdgfdcaba”}S′=“abacdgfdcaba”:

SS 以及 S'S′ 之间的最长公共子串为 {“abacd”}“abacd”,显然,这不是回文。

这种方法确实最好理解的额,但效率确实是低。所以放在编辑器里面跑,都是直接报超出时间限制的错误的,这是最dan疼的

def longestPalindrome(self, s):
        s_inverse = s[::-1]
        max = 0
        maxStr = ""
        if len(s) < 2:
            return s
        for start in range(len(s)):
            for end in range(start+1, len(s)+1):
                if s.count(s_inverse[start:end]) > 0:
                    index = s.index(s_inverse[start:end])
                    start_inverse = len(s) - end
                    if (end - start > max) and (index == start_inverse):
                        max = end - start
                        maxStr = s_inverse[start:end]
        return maxStr

这种方法是巧妙地运用了python 字符串的一些内置函数执行的,代码量很少,但跑起来时间不短

d365f6b344496c137c55b73616f89431.png

大概就是报这样的提醒,就是说你的程序没问题,但是太low了,到后面它都不愿意测试了

    def longestPalindrome(self, s):
        max = 0
        maxStr = ""
        if len(s) < 2:
            return s
        for start in range(len(s)):
            for end in range(len(s)-1, start-1, -1):
                start_copy = start
                end_copy = end
                while s[start_copy] == s[end_copy] and start_copy < end and end_copy >         
                      start:
                    start_copy += 1
                    end_copy -= 1
                if start_copy == end and end_copy == start:
                    if end - start >= max:
                        max = end - start
                        maxStr = s[start: end+1]
        return maxStr

这个方法是借鉴了动态规划的思想,但是也是效率过低,报如上的错误,贴出来可供参考

方法二:动态规划

为了改进暴力法,我们首先观察如何避免在验证回文时进行不必要的重复计算。考虑 {“ababa”}“ababa” 这个示例。如果我们已经知道 {“bab”}“bab” 是回文,那么很明显,{“ababa”}“ababa” 一定是回文,因为它的左首字母和右尾字母是相同的。

我们给出 P(i,j)P(i,j) 的定义如下:

36a269a3e12ca9207d0aa6ec2752e5f4.png

这产生了一个直观的动态规划解法,我们首先初始化一字母和二字母的回文,然后找到所有三字母回文,并依此类推…

复杂度分析

时间复杂度:O(n^2)O(n2), 这里给出我们的运行时间复杂度为 O(n^2)O(n2) 。

空间复杂度:O(n^2)O(n2), 该方法使用 O(n^2)O(n2) 的空间来存储表。

代码如下:

    def longestPalindrome(self, s):
        dpArray = np.zeros([1000, 1000])
        maxIndex = -1
        maxStr = ""
        if len(s) < 2:
            return s
        dpArray[0][0] = 1
        for end in range(1, len(s)):
            dpArray[end][end] = 1
            for start in range(end+1):
                if end == start+1 and s[end] == s[start]:
                    dpArray[start][end] = 1
                if end > start + 1:
                    dpArray[start][end] = dpArray[start+1][end-1] and (s[end] == s[start])
                if end - start >= maxIndex and dpArray[start][end]:
                    maxIndex = end - start
                    maxStr = s[start:end+1]
                    print(maxStr)
        print(dpArray[0:len(s), 0:len(s)])
        return maxStr

看起来确实是牛逼了一些,但不知为何在我的电脑上跑,它还是说超出时间限制,当时真的是快把我给气炸了

方法三:中心扩展算法

事实上,只需使用恒定的空间,我们就可以在 O(n^2)O(n2) 的时间内解决这个问题。

我们观察到回文中心的两侧互为镜像。因此,回文可以从它的中心展开,并且只有 2n - 1个这样的中心。

你可能会问,为什么会是 2n - 1个,而不是 n 个中心?原因在于所含字母数为偶数的回文的中心可以处于两字母之间(例如{“abba”}“abba” 的中心在两个 {‘b’}‘b’ 之间)。

代码如下:

    def longestPalindrome(self, s):
        maxLen = 0
        maxStr1 = ""
        for start_current in range(len(s)):
            if len(s) - start_current <= int(maxLen/2):
                break
            start_head = start_current
            start_last = start_current + 1
            while start_head >= 0 and start_last < len(s) and s[start_head] == s[start_last]:
                start_head -= 1
                start_last += 1
            if start_last - start_head - 1 >= maxLen:
                maxLen = start_last - start_head - 1
                maxStr1 = s[start_head + 1: start_last]
            start_head = start_current
            start_last = start_current
            while start_last < len(s) and start_head >= 0 and s[start_head] == s[start_last]:
                start_head -= 1
                start_last += 1
            if start_last - start_head - 1 >= maxLen:
                maxLen = start_last - start_head - 1
                maxStr1 = s[start_head + 1: start_last]
        return maxStr1

到了这种方法总算是给跑出来了,而且它的速度也算是中等吧!真的是一把鼻涕一把泪啊,本来想再把速度给提高的,但是被之前的失败经历给打击了,容我缓一段时间再来刚,最后把它的执行时间给贴出来

4cd5e84a203d3da86eaa34fc5de2564f.png

2019/3/12 18:09更新

今天再做时就很习惯的用上了动态规划的方法,因为之前也是被动态规划的题目折腾的死去活来的,所以就写了一篇总结,大家可以看看,里面我对解题步骤写了一套模板。

程小新同学:动态规划方法心得​zhuanlan.zhihu.com
243b8ff80117078c5cb73afcce3bfabe.png

代码如下:

class Solution(object):
    # 此题采用动态规划的方法
    # 比较发现:还是将标记矩阵的值设为布尔值最好,方便递归判断
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        if len(s) == 0:
            return ""
        # 首先给标记矩阵初始化,即设置为全False矩阵
        flag_matrix = []
        for s_index in range(len(s)):
            flag_matrix.append([False]*len(s))
        # 设置取得最大回文子串的下标起始值和终点值
        max_start = 0
        max_end = 0
        # 两层for循环来更新标记矩阵的值
        for end in range(len(s)):
            for start in range(end+1):
                if s[end] == s[start]:
                    if end-1 > start+1:
                        flag_matrix[end][start] = flag_matrix[end-1][start+1]
                    else:
                        flag_matrix[end][start] = True
                else:
                    flag_matrix[end][start] = False
                if flag_matrix[end][start] > 0:
                    if end-start > max_end-max_start:
                        max_start = start
                        max_end = end
        return s[max_start:max_end+1]

if __name__ == "__main__":
    s = "aba"
    max_s = Solution().longestPalindrome(s)
    print(max_s)

代码里我对一些注意事项都做了注释,应该还是挺容易理解的。执行效率在13%左右。

5c8a4cb3bcc710c62ce4ada99b5efbe9.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值