最长回文子串--动态规划四部曲
先看题目描述:
给你一个字符串 s,找到 s 中最长的回文子串。
如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
示例 1:
输入: s = "babad"
输出: "bab"
解释: "aba" 同样是符合题意的答案。
示例 2:
输入: s = "cbbd"
输出: "bb"
1. 确定dp数组(dp table)以及下标的含义
首先我们从后往前看,一个下标为i到j的字符串s是不是回文字符串,必须要满足两个条件: (1)s[i]==s[j];
(2)下标为s[i+1]到s[j-1]的字符串是回文字符串;
所以这里用一个二维数组来进行状态的转移,初始化先都填充为false,因为都会从长度为1的状态进行转移,待会在代码中会对长度为一的情况进行处理:
dp=Array.from(Array(s.length+1),()=>Array(s.length+1))//s.length+1纯属个人习惯,其实这里s.length就可以了
这里dp[i][j]的含义就是下标为i到j的字符串是否回文,是就等于true,不是就等于false
2.确定状态转移方程
if(i==j){
//这里dp[i][j]直接赋值为true
dp[i][j]==true
}else if(i==j-1){
//判断i和j相邻的情况
if(s[i]==s[j]){
dp[i][j]=true
}else{
dp[i][j]=false
}
}else{
//最后就是其他情况啦
//首先考虑i==j的情况
if(s[i]==s[j]){
//如果i+1到j-1也是回文字符串,即dp[i+1][j-1]==true
if(dp[i+1][j-1]){
dp[i][j]=true
}else{
dp[i][j]=false
}
}else{
//如果s[i]!=s[j],那么s[i]到s[j]肯定不是回文字符串啦
dp[i][j]=false
}
}
到这里有同学可能就有疑惑了,题目不是让求出最长回文子字符串吗?别急,既然我们已经判断出了任意子串是不是回文字符串,那么只需要在创建一个变量result,在每次判断字符串回文时候时对当前result长度和回文子串长度进行比较,选取较长的赋值给result变量进行保存就可以了,代码如下:
let result=''
if(i==j){
//这里dp[i][j]直接赋值为true
dp[i][j]==true
if(result.length<1){
result=s[i]
}
}else if(i==j-1){
//判断i和j相邻的情况
if(s[i]==s[j]){
dp[i][j]=true
if(result.length<2){
result=s[i]+[j]
}
}else{
dp[i][j]=false
}
}else{
//最后就是其他情况啦
//首先考虑i==j的情况
if(s[i]==s[j]){
//如果i+1到j-1也是回文字符串,即dp[i+1][j-1]==true
if(dp[i+1][j-1]){
dp[i][j]=true
if(result.length<j-i+1){
result=s.substring(i,j-i+1)
}
}else{
dp[i][j]=false
}
}else{
//如果s[i]!=s[j],那么s[i]到s[j]肯定不是回文字符串啦
dp[i][j]=false
}
}
3.dp数组初始化
初始化操作我将其放到了遍历的时候进行单独的判断,所以这里就省略了单独初始化操作步骤
4.确定遍历顺序
刚学动态规划时候,我也对遍历顺序的确定懵懵懂懂,其实遍历顺序的确定与状态转移方程密不可分, 这里状态转移方程dp[i][j]的状态是由dp[i+1][j-1]和s[i]是否等于s[j]来确定的:
| i\j | j=0 | j=1 | j=2 | j=3 |
|---|---|---|---|---|
| i=0 | false | false | false | false |
| i=1 | false | false | false | false |
| i=2 | false | false | false | false |
| i=3 | false | false | false | false |
比如当i=0,j=3时候: dp[0][3]等于true还是false取决于dp[1][2]是true还是false,所以dp[0][3]是由dp[1][2]推导来的,遍历顺序自然就是下往上,从前往后
for (let i = n - 1; i >= 0; i--) {
for (let j = i; j < n; j++) {
//...
}
}
最终题解:
var longestPalindrome = function (s) {
let n = s.length
let dp = Array.from(Array(n + 1), () => Array(n + 1).fill(0))
let res = ''
for (let i = n - 1; i >= 0; i--) {
for (let j = i; j < n; j++) {
if (i == j) {
dp[i][j] = true
if (res.length < 1) {
res = s[i]
}
} else if (i == j - 1) {
if (s[i] == s[j]) {
dp[i][j] = true
if (res.length < 2) {
res = s[i] + s[j]
}
} else {
dp[i][j] = false
}
} else {
if (s[i] == s[j]) {
if (dp[i + 1][j - 1]) {
dp[i][j] = true
if (res.length < j-i+1) {
res = s.substring(i, j + 1)
}
} else {
dp[i][j] = false
}
} else {
dp[i][j] = false
}
}
}
}
return res
};
总结
到这里本篇文章就结束了,其实写这篇文章最初是因为今天下午收到了蔚来的笔试邀请,那当即就搜索一波蔚来笔试面经啊,看看大体会出哪些题目有一个心理准备,就看到了某个作者说笔试时遇到了这道题,我就想想自己能不能思考出来,毕竟刷了不少动态规划题目,也算是对自己这段时间算法学习动态篇的一个考验与总结,并不是说就是这道题最好的解法,只是分享一下自己的思考。如果有想学习算法而又不知道茫茫题海该如何选择的同学,推荐大家去代码随想录进行学习哟,一个很赞的算法学习网站。