「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。
前言
时间过得挺快,这段时间也有点忙,自上次更文以来,时间已经过了快2个月了...
最近做了很多动态规划相关的题目,对于这类题目的解题方法也略知一二,无外乎寻找状态转移方程、考虑题目的边界情况、进行求解等步骤。
题目
给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
示例 1:
输入:s = "bbbab"
输出:4
解释:一个可能的最长回文子序列为 "bbbb" 。
示例 2:
输入:s = "cbbd"
输出:2
解释:一个可能的最长回文子序列为 "bb" 。
提示:
1 <= s.length <= 1000s仅由小写英文字母组成
思考
解答这道题,首先要仔细去理解题意。子序列有两个特征:(1)不改变剩余字符顺序;(2)可以删除某些字符或者不删除任何字符。
其次,对于长度大于 2 的回文子序列,将它首尾的两个字符去除之后,它仍然是个回文子序列。因此我们可以借助动态规划的方法去求解该问题。
我们用dp[i][j]表示字符串 s 的下标范围 [i, j]内的最长回文子序列的长度。长度为 1 的子序列都是回文子序列,那么对任意 0≤i<n,都有 dp[i][i]=1。
当 i < j 时,
- 如果 s[i] 等于 s[j],则状态转移方程为
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])
解答
方法一:动态规划
var longestPalindromeSubseq = function(s) {
let len = s.length
if (!s || len === 0) {
return 0
}
let dp = new Array(len).fill(0).map(() => new Array(len).fill(0))
for (let i = len - 1; i >= 0; i--) { // 需要从len-1开始遍历
dp[i][i] = 1
for (let j = i + 1; j < len; j++) {
if(s[i] === s[j]) {
dp[i][j] = dp[i + 1][j - 1] + 2
} else {
dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1])
}
}
}
return dp[0][len - 1]
};
复杂度分析
- 时间复杂度:O(n^2),其中 n 是字符串 s 的长度。
- 空间复杂度:O(n^2),其中 n 是字符串 s 的长度。