动态规划-最长回文子串

题目来源:https://leetcode-cn.com/problems/longest-palindromic-substring/

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

示例 1:

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


示例 2:

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

 本题有很多种解法,最简单的暴力求解,但是会超时。下面分别说明动态规划法和中心扩散法。

解法一:动态规划法

为什么可以用动态规划呢?

  • 对于一个回文字符串,其子串也必定是回文字符串;
  • 对于一个字符串,若其子串不是回文,则它也必定不是回文;
  • 对于一个字符串,只有其头尾两个字符相等,才有必要继续判断是否为回文。

那么,一个字符串是否是回文就取决于其子串是否是回文。

  • 定义状态:dp[i][j]表示字符串s[i~j]是否为回文。
  • 思考状态转移方程:根据前面的分析可知,一个字符串是否是回文,一看头尾两字符是否相等,若相等则取决于子串是否是回文。于是有:dp[i][j] = (s[i] == s[j]) and dp[i+1][j-1]
  • 初始化:初始化状态矩阵为False。由于任意单个字符都是回文,于是有dp[i][i] = True。
  • 考虑输出:只要得到dp[i][j] = True,就记录起始位置i和字符串长度cur_length,更新记录。(不需要每次都直接取出回文字符串,因为会增加耗时)
  • 状态压缩:减少不必要的额外开销。(下面的代码暂时不做这一步)

根据以上思路书写代码:

class Solution(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        size = len(s)
        if size < 2:             # 考虑特殊状态,无字符或单字符直接返回其本身
            return s
        
        dp = [[False] * size for i in range(size)]     # 初始化状态矩阵
        max_length = 1           # 初始化最大长度
        start = 0                # 初始化最大回文起点

        for i in range(size):      # 初始化对角线(单字符为回文)
            dp[i][i] = True

        for j in range(1, size):        # 终点从s[1]开始
            for i in range(0, j):       # 起点从s[0]开始,到s[j]结束
                if s[i] == s[j]:                   # 头尾字符相等
                    if j - i < 3:                  # 长度小于3,2~0字符此时均可判断为回文
                        dp[i][j] = True 
                    else:                          # 取决于子字符串的状态(是否为回文)
                        dp[i][j] = dp[i+1][j-1]
                else:
                    dp[i][j] = False               # 头尾不相等,一定不是回文

                if dp[i][j]:                       # 每确定一个回文,更新一次最大长度和回文起点
                    cur_length = j - i + 1
                    if cur_length > max_length:
                        max_length = cur_length
                        start = i
        return s[start:start + max_length]      
        

时间复杂度:O\left (N^{2} \right ) (两层循环)

空间复杂度:O\left (N^{2} \right ) (状态矩阵dp的大小为n*n)

 

解法二:中心扩散法

动态规划法相当于从大字符串回溯到小字符串来完成判断,那么能不能从中间某个字符开始向外扩散实现判断呢?

中心扩散法就是这样的思想,以字符串中任意一个字符为初始点,向左右逐单位扩散,若扩散后还是回文,则继续扩散,否则记录起点和回文长度。

根据字符串长度的不同,分为奇数中心和偶数中心:

  • 字符串长度为奇数:中心是单字符,如'aba'的中心是'b';
  • 字符串长度为偶数:中心是两个字符的间隙,如'abba'的中心是'bb'间的间隙。

设计一种算法,兼容这两种情况。

class Solution(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        n = len(s)

        if n < 2:             # 特殊情况,单字符或空字符
            return s
        
        max_length = 1
        ans = s[0]

        for i in range(n):
            palindrome_odd, max_odd = self.find_palindrome(s, n, i, i)
            palindrome_eve, max_eve = self.find_palindrome(s, n, i, i + 1)
            palindrome = palindrome_eve if max_eve >= max_odd else palindrome_odd
            if len(palindrome) >= max_length:       # 每次都更新最大回文及其长度
                max_length = len(palindrome)
                ans = palindrome
        return ans            

        
    def find_palindrome(self, s, n, i, j):          # 从中心开始扩散,寻找最大回文及其长度
        while i >= 0 and j < n and s[i] == s[j]:    
            i -= 1
            j += 1
        return s[i+1:j], (j - i - 1)       # 最后一次循环后i和j多改变了一次
            

                

时间复杂度:O\left (N^{2} \right )  (枚举扩散中心为O\left (N),寻找最大回文也为O\left (N)

空间复杂度:O\left (1) (变量数为常数,不随N改变(递归时才会每次分配不同存储空间))

 

解法三:马拉车算法

暂时放置。。

 

参考:

https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zhong-xin-kuo-san-dong-tai-gui-hua-by-liweiwei1419/

https://leetcode-cn.com/problems/longest-palindromic-substring/solution/gao-hao-dong-tai-gui-hua-he-zhong-xin-tuo-zhan-zhu/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值