vlambda博客
学习文章列表

动态规划与中心扩展算法

今天要挑战的是“最长回文子串”。

难度中等

题目描述:

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

示例1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。

示例2:

输入: "cbbd"
输出: "bb"

题目原始链接

方法1:动态规划

什么是动态规范:

动态规划是求解某类问题的一种思维,而不是像快速排序这样的算法,它没有一个标准的数学表达式或明确定义。它的核心是把原问题分解为相对简单的子问题的方式进行求解复杂问题的思维方法。

动态规划过程如下:

  1. 将一个大问题转化成几个小问题;
  2. 求解小问题;
  3. 得出大问题的解。

用一句话来说就是大事化小,小事化了。

求解思维:

如果一个字符串是回文,并且长度大于 2,那么将它首尾的两个字母去除之后,它仍然是个回文串。

用P(i,j)表示字符串s的第i到j个字母组成的子字符串是否为回文串:

这里的其它情况包含两种可能性:

  1. s[i, j] 本身不是一个回文串;

  2. i > j,此时 s[i, j]本身不合法。

由此,动态规划的状态转移方程可归纳为:

P(i,j)=P(i+1,j−1)∧(Si==Sj)

即只有s[i+1:j-1]是回文串,并且s的第i和j个字母相同时,s[i:j]才会是回文串。

最后考虑边界情况,字符串长度小于等于2(即长度为2或1)的两种情况。

  1. 长度为1时,肯定为回文;
  2. 长度为2时,若两个字母相等为回文,否则不是回文。

方程表示如下:

根据以上思路来实现代码。

代码如下:

class Solution:
    def longestPalindrome(self, s: str) -> str:
        max_len = len(s)
        p = [[False] * max_len for _ in range(max_len)] 
        result = ""
        for l in range(1, max_len+1):
            for left in range(max_len):
                right = l + left - 1
                if right >= max_len:
                    break
                if l == 1:
                    p[left][right] = True
                elif l == 2:
                    p[left][right] = (s[left] == s[right])
                else:
                    p[left][right] = (p[left+1][right-1and s[left] == s[right])
                if p[left][right] and l > len(result):
                    result = s[left:right+1]
        return result

复杂度分析:

  • 时间复杂度

    整个过程需要遍历两遍,故时间复杂度为O(n^2)。

  • 空间复杂度

    过程中需要存储字符串平方的数据量,故空间复杂度也为O(n^2)。

结果:

方法2:中心扩展算法

什么是中心扩展算法:

中心扩展方法就是基于回文串一定是对称的这一点,遍历字符串选择一个中心,进行左右扩展,判断左右字符是否相等即可。

解题思路:

遍历字符串,分别以当前的一个字符和两个字符串为中心向两边进行扩展直到不满足条件,从中比较得到最长的回文子串。

代码如下:

class Solution:
    def longestPalindrome(self, s: str) -> str:
        left, right = 00
        for i in range(len(s)):
            l1, r1 = self.expandCenter(s, i, i)
            l2, r2 = self.expandCenter(s, i, i+1)
            if r1 - l1 > right - left:
                left, right = l1, r1
            if r2 - l2 > right - left:
                left, right = l2, r2
        return s[left:right+1]

    def expandCenter(self, s, left, right):
        while left >= 0 and right < len(s) and s[left] == s[right]:
            left -= 1
            right += 1
        return left+1, right-1

复杂度分析:

  • 时间复杂度

    需要遍历一遍,并最多会向外扩展O(n)次,故时间复杂度为O(n^2)。

  • 空间复杂度

    没有存储中间值,只需要进行比较,故空间复杂度为O(1)。

结果:

参考资料

https://www.zhihu.com/question/23995189

https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zui-chang-hui-wen-zi-chuan-by-leetcode-solution/