动态规划|最长回文子序列
今天一起来学习Leetcode第 516 题:最长回文子序列。
题目描述
题目分析
首先回文字符串指的是形如“a”,“aa”,‘’aba‘‘,’‘abba’‘这样的字符串。这是一道非常经典的「动态规划」的题目。
碰到动态规划的题目,最重要的就是写出「状态的定义」和「状态转移方程」。
而在这两者中,我个人觉得状态的定义是比较难想到的,因为这其中「感觉」很重要,为什么状态dp[i][j]
要这么定义?这其实需要多写一些动态规划的题目才能找到这种感觉。
而状态转移方程我恰恰觉得比较容易,因为我们只需要知道我们人是怎么思考的,然后遵循自己的思考的方式不断让程序填充dp
数组就行。
在这一题中,我们定义一个二维数组dp[n][n]
,其中 n
为 输入字符串的长度。dp[i][j]
代表从第i
到第j
个字符串的最长回文子序列的长度。比如说在bbbab
这个字符串中,我们可以看到bab
这个子串的最长回文子序列的长度为3,所以dp[2][4]=3
。同时我们也可以初始化:
for i
因为一个字符的最长回文字符串的长度为1
。
定义好了状态dp[i][j]
之后,我们看怎么梳理状态转移方程。我们看下面这幅图:
那也就是说,我们可以总结如下的状态转移方程:
当s[i]==s[j]
时:
当s[i]!=s[j]
时:
当然,我们就算想到了上述的状态转移方程,距离我们写出完整的代码还是有一段距离。
「在动态规划问题中,用图示的方法来理解更新dp
数组的过程是一个非常好用的方法。」
我们可以用下图来展示dp
数组的更新过程:
图示非常清晰,我们在更新dp[i][j]
的值的时候,需要知道dp[i+1][j-1]
, dp[i+1][j]
和 dp[i][j-1]
的值。那么dp
数组的更新方向应该就是从左上角往右下角。并不断往右上角逼近,如下图所示:
有了上面的分析,我们基本就可以写出题目的代码
题目代码
class Solution:
算法时间和空间复杂度都是O(n^2)
。
总结
动态规划的题目感觉现在已经成了笔面试的标配,像 「最长回文子序列」 这类经典动态规划的题目时是非常容易碰到的,所以我们还是应该反复思考和学习。