最长回文子序列想要按照同样思路去求解就比较麻烦,既然以字符结尾的方法求解遇阻,那么可以考虑区间求解,也是动态规划常见的另一种思路;区间求解即用
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示
s
[
i
:
j
]
s[i:j]
s[i:j](包含
j
j
j,下同)的最长回文子序列
p
a
l
pal
pal,此时不要求
p
a
l
pal
pal以
s
[
i
]
s[i]
s[i]开头或以
s
[
j
]
s[j]
s[j]结尾;
接下来是描述
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]的转移方程或者递归方程:
d
p
[
i
]
[
j
]
=
{
d
p
[
i
+
1
]
[
j
−
1
]
+
2
if
s
[
i
]
=
=
s
[
j
]
m
a
x
(
d
p
[
i
]
[
j
−
1
]
,
d
p
[
i
+
1
]
[
j
]
)
if
s
[
i
]
≠
s
[
j
]
dp[i][j]=\begin{cases} dp[i+1][j-1] + 2 &\text{if } s[i] == s[j]\\ max(dp[i][j-1],\ dp[i+1][j]) &\text{if } s[i] \ne s[j] \end{cases}
dp[i][j]={dp[i+1][j−1]+2max(dp[i][j−1],dp[i+1][j])if s[i]==s[j]if s[i]=s[j]
上述方程表示,子序列
s
[
i
,
j
]
s[i,j]
s[i,j]的最长回文子序列存在下列两种情况:
一是
s
[
i
]
=
=
s
[
j
]
s[i] == s[j]
s[i]==s[j],那么
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] =
d
p
[
i
+
1
]
[
j
−
1
]
dp[i+1][j-1]
dp[i+1][j−1] + 2,如下图(@labuladong)
二是
s
[
i
]
≠
s
[
j
]
s[i] \ne s[j]
s[i]=s[j],此时
s
[
i
]
s[i]
s[i]、
s
[
j
]
s[j]
s[j]最多只有一个对
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]有贡献,那么
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
]
[
j
−
1
]
,
d
p
[
i
+
1
]
[
j
]
)
dp[i][j]=max(dp[i][j-1], dp[i+1][j])
dp[i][j]=max(dp[i][j−1],dp[i+1][j]),如下图
需要处理初始值(base case);包括
i
≤
j
i \le j
i≤j(子序列存在条件)以及
d
p
[
i
]
[
i
]
=
=
1
dp[i][i] == 1
dp[i][i]==1;此时整体思路可用下图表示:
图中左下角为0和对角线为1均是要满足base case;目标是求右上角值;为了利用动态规划,根据
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]的定义,有上图第二部分所示的两种回溯方法,这里选择第二种,横向+反向遍历,代码如下
classSolution:deflongestPalindromeSubseq(self, s:str)->int:# 参考自labuladong的解法,思路如下# 最长回文子串解法之一是以每个位置i对应字符c_i为回文子串的结尾,利用前面结果求解# 最长回文子序列想要按照同样思路去求解就比较麻烦,既然以字符结尾求解遇阻,那么可以考虑区间求解# 即用dp[i][j]表示s[i:j+1]的最长回文子序列pal,此时不要求pal以s[i]开头或以s[j]结尾# 接下来是描述dp[i][j]的转移方程或者递归方程# dp[i][j] = dp[i+1][j-1] + 2 if (s[i] == s[j]) else max(dp[i][j-1], dp[i+1][j])# base case: i <= j AND dp[i][i] == 1
n =len(s)if n <=1:return n
dp =[[0for _ inrange(n)]for _ inrange(n)]# base casefor i inrange(n):# base case
dp[i][i]=1for i inrange(n-1,-1,-1):# 反向遍历(回溯)for j inrange(i+1, n):# 横向遍历(回溯)
dp[i][j]= dp[i+1][j-1]+2if(s[i]== s[j])elsemax(dp[i][j-1], dp[i+1][j])return dp[0][n-1]