代码随想录训练营 Day46打卡 动态规划 part13
一、力扣647. 回文子串
给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。
子字符串 是字符串中的由连续字符组成的一个序列。
示例 1:
输入:s = “abc”
输出:3
解释:三个回文子串: “a”, “b”, “c”
示例 2:
输入:s = “aaa”
输出:6
解释:6个回文子串: “a”, “a”, “a”, “aa”, “aa”, “aaa”
![](https://img-blog.csdnimg.cn/img_convert/5f4396c4e267c4abbd76a9d52a0f3a00.png)
-
确定 dp 数组的定义
dp[i][j] 是一个布尔值,用来表示字符串 s 的区间 [i, j] 是否为回文子串。如果是回文子串,则 dp[i][j] = True,否则为 False。 -
状态转移方程
当 s[i] == s[j] 时:
有三种情况需要考虑:
(1)当 i == j:同一个字符,显然是回文子串。
此时 dp[i][j] = True。
(2)当 j - i == 1:两个相邻的相同字符,也是回文子串。
此时 dp[i][j] = True。
(3)当 j - i > 1:如果 s[i+1:j-1] 是回文,并且 s[i] == s[j],那么 s[i:j+1] 也是回文子串。
此时 dp[i][j] = dp[i+1][j-1]。
当 s[i] != s[j] 时:
dp[i][j] = False,因为两端字符不同,不可能是回文。
-
dp 数组的初始化
dp[i][j] 初始时默认都是 False,即假设所有子串都不是回文子串。
遍历过程中再依据条件更新 dp[i][j] 的值。 -
确定遍历顺序
因为 dp[i][j] 依赖于 dp[i+1][j-1] 的结果,所以遍历时需要从下到上,从左到右进行,确保 dp[i+1][j-1] 已经被计算。 -
最终结果
在遍历过程中,每当 dp[i][j] 为 True 时,意味着发现一个新的回文子串,结果 result 自增 1。
举例,输入:“aaa”,dp[i][j]状态如下:
代码实现
class Solution:
def countSubstrings(self, s: str) -> int:
# 创建一个二维数组 dp,初始化为 False
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]:
# 情况一:同一个字符 i == j
# 情况二:两个相邻字符 j - i == 1
if j - i <= 1:
dp[i][j] = True
result += 1
# 情况三:i 与 j 之间至少有一个字符
elif dp[i + 1][j - 1]:
dp[i][j] = True
result += 1
# 返回回文子串的总数
return result
二、力扣516. 最长回文子序列
给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
示例 1:
输入:s = “bbbab”
输出:4
解释:一个可能的最长回文子序列为 “bbbb” 。
示例 2:
输入:s = “cbbd”
输出:2
解释:一个可能的最长回文子序列为 “bb” 。
-
确定 dp 数组的定义
dp[i][j] 表示字符串 s 在区间 [i, j] 范围内的最长回文子序列的长度。 -
状态转移方程
当 s[i] == s[j] 时:
如果字符串的两个端点字符相同,那么它们可以构成回文子序列的一部分,dp[i][j] 可以从 dp[i+1][j-1] 转移过来,再加上这两个相同字符的长度 2:
状态转移方程:dp[i][j] = dp[i+1][j-1] + 2
当 s[i] != s[j] 时:
如果两个端点字符不同,那么最长回文子序列不包括这两个字符。我们需要分别比较去掉左边的字符 s[i] 或去掉右边的字符 s[j] 之后的情况,取两者之间的最大值:
状态转移方程:dp[i][j] = max(dp[i+1][j], dp[i][j-1])
![](https://img-blog.csdnimg.cn/img_convert/8c6a42dd0efc502cf9b30383101535a5.jpeg)
-
dp 数组的初始化
当 i == j 时,dp[i][j] = 1,表示字符串中单个字符的回文子序列长度为 1。
其他情况下,dp[i][j] 初始值为 0,等待后续填充。 -
确定遍历顺序
因为 dp[i][j] 依赖于 dp[i+1][j-1]、dp[i+1][j] 和 dp[i][j-1] 的结果,因此在遍历时需要从下到上(i 递减),从左到右(j 递增)进行遍历,以确保在计算 dp[i][j] 时,dp[i+1][j-1] 已经被计算过。
-
最终结果
最终 dp[0][len(s)-1] 的值就是整个字符串 s 的最长回文子序列的长度。
代码实现
class Solution:
def longestPalindromeSubseq(self, s: str) -> int:
n = len(s)
# 创建一个二维数组 dp,初始化为 0
dp = [[0] * n for _ in range(n)]
# 初始化:单个字符的回文子序列长度为 1
for i in range(n):
dp[i][i] = 1
# 遍历字符串,填充 dp 数组
for i in range(n-1, -1, -1): # 从下到上遍历
for j in range(i+1, n): # 从左到右遍历
if s[i] == s[j]:
# 情况一:s[i] == s[j]
dp[i][j] = dp[i+1][j-1] + 2
else:
# 情况二:s[i] != s[j]
dp[i][j] = max(dp[i+1][j], dp[i][j-1])
# 返回 dp[0][n-1],即整个字符串的最长回文子序列的长度
return dp[0][n-1]