代码随想录算法训练营Day40 | Leetcode 647. 回文子串、516.最长回文子序列

代码随想录算法训练营Day40 | Leetcode 647. 回文子串、516.最长回文子序列

一、回文子串

相关题目:Leetcode647
文档讲解:Leetcode647
视频讲解:Leetcode647

1. Leetcode647. 回文子串

给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。子字符串 是字符串中的由连续字符组成的一个序列。
提示:

  • 1 <= s.length <= 1000
  • s 由小写英文字母组成
  • 思路:
    • 动规五部曲
      • 确定 dp 数组以及下标的含义:本题如果定义 dp[i] 为下标 i 结尾的字符串有 dp[i] 个回文串的话,很难找到递归关系。需要观察回文串的性质,如图:
        请添加图片描述
        在判断字符串 s 是否是回文时,如果已知 s[i+1],…,s[j-1] 这个子串是回文的,那么只需要比较 s[i] 和 s[j] 这两个元素是否相同,如果相同的话,这个字符串 s 就是回文串。因此 dp 数组应该要定义成二维 dp 数组,dp[i][j] 表示区间范围 [i,j] (注意是左闭右闭)的子串是否是回文子串,如果是 dp[i][j] 为 true,否则为 false。
      • 确定递推公式:在确定递推公式时需要分析如下几种情况:
        • 当 s[i] 与 s[j] 不相等,则 dp[i][j] 一定是 false。
        • 当 s[i] 与 s[j] 相等,有如下三种情况:
          • 情况一:下标 i 与 j 相同,同一个字符例如 a,当然是回文子串;
          • 情况二:下标 i 与 j 相差为 1,例如 aa,也是回文子串;
          • 情况三:下标 i 与 j 相差大于 1 时,此时 s[i] 与 s[j] 已经相同,只需看 i+1 至 j-1 区间是不是回文即可,也就是看 dp[i + 1][j - 1] 是否为 true。
      • dp 数组如何初始化:刚开始无法匹配上,所以 dp[i][j] 都初始化为 false。
      • 确定遍历顺序:从递推公式中可以看出,情况三是根据 dp[i + 1][j - 1] 是否为 true,再对 dp[i][j] 进行赋值 true 的。dp[i + 1][j - 1] 在 dp[i][j] 的左下角,所以一定要从下到上,从左到右遍历,这样保证 dp[i + 1][j - 1] 都是经过计算的。
      • 举例推导 dp 数组:以输入:“aaa” 为例,dp[i][j] 状态如下:
        请添加图片描述
  • 动规
class Solution:
    def countSubstrings(self, s: str) -> int:
        dp = [[False] * len(s) for _ in range(len(s))]
        result = 0
        for i in range(len(s)-1, -1, -1): #注意遍历顺序
            for j in range(i, len(s)):
                if s[i] == s[j]:
                    if j - i <= 1: #情况一 和 情况二
                        result += 1
                        dp[i][j] = True
                    elif dp[i+1][j-1]: #情况三
                        result += 1
                        dp[i][j] = True
        return result
        
###简洁版
class Solution:
    def countSubstrings(self, s: str) -> int:
        dp = [[False] * len(s) for _ in range(len(s))]
        result = 0
        for i in range(len(s)-1, -1, -1): #注意遍历顺序
            for j in range(i, len(s)):
                if s[i] == s[j] and (j - i <= 1 or dp[i+1][j-1]): 
                    result += 1
                    dp[i][j] = True
        return result
  • 双指针法
class Solution:
    def countSubstrings(self, s: str) -> int:
        result = 0
        for i in range(len(s)):
            result += self.extend(s, i, i, len(s)) #以i为中心
            result += self.extend(s, i, i+1, len(s)) #以i和i+1为中心
        return result
    
    def extend(self, s, i, j, n):
        res = 0
        while i >= 0 and j < n and s[i] == s[j]:
            i -= 1
            j += 1
            res += 1
        return res

二、最长回文子序列

相关题目:Leetcode516
文档讲解:Leetcode516
视频讲解:Leetcode516

1. Leetcode516.最长回文子序列

给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
提示:

  • 1 <= s.length <= 1000
  • s 仅由小写英文字母组成
  • 思路:
    • 动规五部曲
      • 确定 dp 数组以及下标的含义:dp[i][j] 表示字符串 s 在 [i, j] 范围内最长的回文子序列的长度为 dp[i][j]。

      • 确定递推公式

        • 如果 s[i] 与 s[j] 相同,那么 dp[i][j] = dp[i + 1][j - 1] + 2,如图:
          请添加图片描述
        • 如果 s[i] 与 s[j] 不相同,说明 s[i] 和 s[j] 的同时加入并不能增加 [i,j] 区间回文子序列的长度,那么可以分别加入 s[i]、s[j] 看看是否可以组成最长的回文子序列:
          • 加 入s[j] 的回文子序列长度为 dp[i + 1][j],
          • 加入 s[i] 的回文子序列长度为 dp[i][j - 1],

        那么 dp[i][j] 取最大的,即:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])
        请添加图片描述

      • dp 数组如何初始化:从递推公式:dp[i][j] = dp[i + 1][j - 1] + 2 可以看出递推公式是计算到 i 和 j 相同时候的情况,所以需要初始化 dp[i][i]。当 i 与 j 相同,dp[i][j] 等于 1,即:一个字符的回文子序列长度就是 1。其他情况 dp[i][j] 初始为 0 ,这样递推公式:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]) 中 dp[i][j] 才不会被初始值覆盖。

      • 确定遍历顺序:从递归公式中,可以看出 dp[i][j] 依赖于 dp[i + 1][j - 1] ,dp[i + 1][j] 和 dp[i][j - 1],如图:
        请添加图片描述
        所以遍历 i 的时候一定要从下到上遍历,这样才能保证下一行的数据是经过计算的;而 j 可以正常从左向右遍历。

      • 举例推导 dp 数组:以输入 s:“cbbd” 为例,dp 数组状态如图:
        请添加图片描述

  • 动规
class Solution:
    def longestPalindromeSubseq(self, s: str) -> int:
        dp = [[0] * len(s) for _ in range(len(s))]
        for i in range(len(s)):
            dp[i][i] = 1
        for i in range(len(s)-1, -1, -1):
            for j in range(i+1, len(s)):
                if s[i] == s[j]:
                    dp[i][j] = dp[i+1][j-1] + 2
                else:
                    dp[i][j] = max(dp[i+1][j], dp[i][j-1])
        return dp[0][-1]
### LeetCode 第 5 题 '最长回文子串' 的 Python 解法 对于给定字符串 `s`,返回其中的最长回文子串是一个经典算法问题。一种高效的解决方案是利用中心扩展方法来寻找可能的最大长度回文。 #### 中心扩展法解析 该方法基于观察到的一个事实:一个回文串可以由中间向两端不断扩散而得。因此可以从每一个字符位置出发尝试构建尽可能大的回文序列[^1]。 具体来说: - 对于每个字符作为单个字符的中心点; - 或者两个相同相邻字符作为一个整体中心点; - 向两侧延伸直到遇到不匹配的情况为止; 记录下每次找到的有效回文串及其起始索引和结束索引,并更新全局最优解。 下面是具体的 Python 实现代码: ```python def longest_palindrome(s: str) -> str: if not s or len(s) == 0: return "" start, end = 0, 0 for i in range(len(s)): len1 = expand_around_center(s, i, i) len2 = expand_around_center(s, i, i + 1) max_len = max(len1, len2) if max_len > end - start: start = i - (max_len - 1) // 2 end = i + max_len // 2 return s[start:end + 1] def expand_around_center(s: str, left: int, right: int) -> int: L, R = left, right while L >= 0 and R < len(s) and s[L] == s[R]: L -= 1 R += 1 return R - L - 1 ``` 此函数通过遍历整个输入字符串并调用辅助函数 `expand_around_center()` 来计算以当前位置为中心能够形成的最长回文串长度。最终得到的结果即为所求的最大回文子串
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值