算法通关之路
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.5 最长回文子序列

和上面的题目类似,只是这道题从回文子串变成了回文子序列。力扣(LeetCode)上有很多题目都是这样的,只是将子串改成子序列就变成了另一道题。下面通过这两道题来看一下回文子串和回文子序列的不同。

题目描述(第516题)

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

示例1

输入:"bbbab"

输出:4

一个可能的最长回文子序列为"bbbb"。

示例2

输入:"cbbd"

输出:2

一个可能的最长回文子序列为"bb"。

思路

如果还是采取最长回文子串的思路,问题会比较复杂,因为子序列的数量会很多,我们考虑换一种思路。绝大多数字符串子序列的题目都可以使用动态规划来解决,从而避免让算法达到指数级复杂度。

假设字符串中间部分的最长回文子序列长度已经算出,下面比较两侧的字符。

● 如果两侧字符相同,那么新的最长回文子序列就加2。

● 如果两侧字符不同,那么新的最长回文子序列不变。

具体如下。

● 初始化一个n行n列的dp数组,其中n表示字符串的长度,dp[i][j]表示字符串s[i:j+1]中的最大回文子序列的长度。

● 使用两层循环找出所有的子串。

● 对每一个子串,我们考虑如下3种情况。

1.如果子串长度为1,那么dp[i][j]=1。

2.如果s[i]==s[j],那么可以进行扩展 dp[i+1][j-1]+2。

3.如果s[i]!=s[j],则无法进行扩展,我们取dp[i+1][j]和dp[i][j-1]较大者即可。

● 最后返回dp[0][n-1]即可。

由于dp[i][…]依赖于dp[i+1][…],因此外层循环需要从后往前进行遍历。

代码

复杂度分析

● 时间复杂度:O(n2),其中n为字符串长度。

● 空间复杂度:这里使用了O(n2)的二维dp数组来存储中间状态,因此空间复杂度为O(n2),其中n为字符串长度。

由于dp[i][j]仅依赖dp[i+1][j]和dp[i][j-1],因此可以使用滚动数组技巧使空间复杂度降低到O(n),这个技巧将在动态规划章节进行讲解。

复杂度分析

● 时间复杂度:O(n2),其中n为字符串长度。

● 空间复杂度:我们使用了两个长度为n的数组来存储中间状态,因此空间复杂度为O(n),其中n为字符串长度。

实际上,我们可以只使用一个数组和一个pre变量来解决这个题目,这次pre不再是数组而是一个数,具体解法留给读者来思考。