
LeetCodeCampsDay46动态规划part13
使用dp解决回文串和回文序列问题
647. 回文子串
https://leetcode.cn/problems/palindromic-substrings/
给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。
子字符串 是字符串中的由连续字符组成的一个序列。
示例 1:
输入:s = "abc"
输出:3
解释:三个回文子串: "a", "b", "c"示例 2:
输入:s = "aaa"
输出:6
解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"提示:
1 <= s.length <= 1000s由小写英文字母组成
普通思路
本题和前面写过的26. 单词拆分有点像
需要将s拆分成长度为1、2、…L的子串,做法:使用双指针,i作为子串结尾位置;j作为子串起点位置,从而对所有子串进行判断是否为回文串;
比如s= ‘abc’,将得到a, ab, b, abc, bc, c 再对每个子串判断是否为回文串(调用个函数即可)
但这种写法会超时,因为有太多的重复内容被反复计算
所以需要有dp的方法来判断回文串
普通代码(超时)
class Solution:
def countSubstrings(self, s: str) -> int:
def isLegal(s: str) -> bool:
left = 0
right = len(s) - 1
while left < right:
if s[left] != s[right]:
return False
left += 1
right -= 1
return True
L = len(s)
res = 0
for i in range(1, L + 1):
for j in range(i):
# print(s[j: i])
if isLegal(s[j: i]):
res += 1
return res动态规划思路
一个更好的思路,把问题拆成子问题
比如为了判断abcba是否为子串,可以先判断bcb是否为子串->判断c是否为子串
所以问题可以变成


同时使用两个指针遍历,和普通思路一样,也需要将s拆分成长度为1、2、…L的子串s[j: i](左闭右闭)
- 如果长度为1,即s[j]==s[i]且j==i,那一定是回文,令dp[j][i]=1
- 如果长度为2,即s[j]==s[i]且j+1==i,也是回文,令dp[j][i]=1
- 如果长度大于2,比如abcba,即s[j]==s[i]=a,且j+1<i,此时需要判断s[j+1:i-1](左闭右闭)这一段(bcb)是否为回文串,如果bcb已经是回文串(即令dp[j + 1][i - 1]=1),那s[j:i](左闭右闭),也是回文,令dp[j][i]=1;否则就不是回文
反之
- 如果s[j]!=s[i],那一定不是回文串
动态规划代码
class Solution:
def countSubstrings(self, s: str) -> int:
# 下面使用动态规划解决
L = len(s)
res = 0
dp = [[False] * (L + 1) for _ in range(L + 1)]
# 全部初始化为False即可
for i in range(1, L + 1):
for j in range(1, i + 1):
# print(s[j - 1], s[i - 1])
if s[j - 1] == s[i - 1]:
if j == i:
# 同一个字符且位置相同
res += 1
dp[j][i] = True
elif j + 1 == i:
# 相邻两个字符
res += 1
dp[j][i] = True
else:
# j与i之间有其它字符,此时要判断s[j+1: i-1](左闭右闭)是否为回文,如果它也是回文,才能说明s[j: i](左闭右闭)是回文
if dp[j + 1][i - 1]:
dp[j][i] = True
res += 1
# print(dp)
return res注意这里的遍历顺序,最好是i从后向前遍历,因为在状态转换时需要使用dp[i + 1][j - 1]
所以i的范围从[L-1, 0]而j的范围从[i, L-1],而dp的含义还是s[j-1,i+1]这段字符串是否为回文
代码改进
# 下面使用动态规划解决
L = len(s)
res = 0
dp = [[False] * (L) for _ in range(L)]
# 全部初始化为False即可
for i in range(L - 1, -1, -1):
for j in range(i, L):
# print(s[j - 1], s[i - 1])
if s[j] == s[i]:
if j == i:
# 同一个字符且位置相同
res += 1
dp[i][j] = True
elif j - 1 == i:
# 相邻两个字符
res += 1
dp[i][j] = True
else:
# j与i之间有其它字符,此时要判断s[j+1: i-1](左闭右闭)是否为回文,如果它也是回文,才能说明s[j: i](左闭右闭)是回文
if dp[i + 1][j - 1]:
dp[i][j] = True
res += 1
# print(dp)
return res516. 最长回文子序列
https://leetcode.cn/problems/longest-palindromic-subsequence/
给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
示例 1:
输入:s = "bbbab"
输出:4
解释:一个可能的最长回文子序列为 "bbbb" 。示例 2:
输入:s = "cbbd"
输出:2
解释:一个可能的最长回文子序列为 "bb" 。提示:
1 <= s.length <= 1000s仅由小写英文字母组成
动态规划思路
注意这题是回文子序列(不要求连续)
仍然可以使用dp数组做,和647. 回文子串思路相似
- dp下标与含义
dp[i][j]表示s[i:j](左闭右闭)的最长的回文子序列长度
- 递推公式
在判断回文子串的题目中,关键逻辑就是看s[i]与s[j]是否相同。
如果s[i]与s[j]相同,那么dp[i][j] = dp[i + 1][j - 1] + 2
如图:


如果不相同,说明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]);


- 初始化
首先要考虑当i 和j 相同的情况,从递推公式:dp[i][j] = dp[i + 1][j - 1] + 2; 可以看出 递推公式是计算不到 i 和j相同时候的情况。
所以需要手动的将i==j的情况全部初始化为1,其它值初始化为0
当然,也可以不手动初始化,但需要在s[i]==s[j]时,手动添加判断条件,如果i==j则赋值为1;而且j - 1==i时需要手动赋值为2
- 遍历顺序
从递归公式中,可以看出,dp[i][j] 依赖于 dp[i + 1][j - 1] ,dp[i + 1][j] 和 dp[i][j - 1],如图:


所以遍历i的时候一定要从下到上遍历,这样才能保证下一行的数据是经过计算的。
动态规划代码
- 时间复杂度O(N^2)
- 空间复杂度O(N^2)
class Solution:
def longestPalindromeSubseq(self, s: str) -> int:
# dp下标与含义
# dp[i][j]表示s[j:i](左闭右闭)的最长回文子序列
L = len(s)
dp = [[0] * L for _ in range(L)]
for i in range(L - 1, -1, -1):
for j in range(i, L):
if s[i] == s[j]:
if i == j:
dp[i][j] = 1
elif j - 1 == i:
dp[i][j] = 2
elif dp[i + 1][j - 1] != 0:
dp[i][j] = dp[i + 1][j - 1] + 2
else:
dp[i][j] = max(dp[i][j - 1], dp[i + 1][j])
return dp[0][-1]动态规划代码二
手动初始化
- 时间复杂度O(N^2)
- 空间复杂度O(N^2)
class Solution:
def longestPalindromeSubseq(self, s: str) -> int:
# dp下标与含义
# dp[i][j]表示s[j:i](左闭右闭)的最长回文子序列
L = len(s)
dp = [[0] * L for _ in range(L)]
# 对角线全为1
for i in range(L):
dp[i][i] = 1
for i in range(L - 1, -1, -1):
for j in range(i + 1, L):
if s[i] == s[j]:
dp[i][j] = dp[i + 1][j - 1] + 2
else:
dp[i][j] = max(dp[i][j - 1], dp[i + 1][j])
return dp[0][-1]- 文章作者:
- Lthero博客
- 版权声明:
- 本博客所有文章除特别声明外,均采用CC BY-NC-SA 4.0许可协议。转载请注明来自 Lthero博客!



