5. 最长回文子串 - 力扣(LeetCode)
发布:2021年9月22日14:02:13
问题描述及示例
给你一个字符串 s,找到 s 中最长的回文子串。
示例 1:
输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
示例 2:
输入:s = “cbbd”
输出:“bb”
示例 3:
输入:s = “a”
输出:“a”
示例 4:
输入:s = “ac”
输出:“a”
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-palindromic-substring
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
提示:
1 <= s.length <= 1000
s 仅由数字和英文字母(大写和/或小写)组成
我的题解(动态规划)
有关动态规划的思路总结,之前写过一个相关的题解博客:
里面也有我写的其他动态规划题解,一些通用的步骤都是一样的,也可以作为参考,进行对照思考。
那就按照上面提到的那几个步骤开始分解:
①确定 dp
数组的含义。这是最关键的一步。我把 dp
数组设置为一个 s.length × s.length
的二维数组并给每个元素填充为 true
。其中,dp[i][j]
是一个布尔值,表示 s
字符串中从下标 i
到下标 j
所形成的子串是否为一个回文字符串。
②确定状态转移方程并顺便确定 dp
数组的初始化操作。判断 s[i] ~ s[j]
是否为一个回文字符串需要判断两个因素:
s[i]
是否等于s[j]
;s[i+1] ~ s[j-1]
是否为一个回文字符串。
于是状态转移方程就可以很容易写出来了:dp[i][j] = s[i] === s[j] && dp[i+1][j-1];
。可以看到,其实 dp[i+1][j-1]
就是 dp[i][j]
左下角的那个元素。
当 i > j
时,显然 dp[i][j]
是无意义的,但是为了方便后续计算,还是得把它们初始化为 true
才行,否则就要加上一些多余的判断。
而 dp
数组的初始化操作其实就是当 i=j
时的情况,此时子串只有一个字符,自然可以形成回文结构。所以上面那张表格中的对角线处(浅灰色单元格)的所有单元格都应该填上 true
。
③确定遍历顺序。前面说到了:计算 dp[i][j]
要用到其左下角的那个元素 dp[i+1][j-1]
,所以应该填充 dp
数组时,应当是从右下到左上的顺序。大致如下图红色箭头所示:
注意,第一列和最后一行的元素都不在计算范围内。
详细解释请看下方注释:
/**
* @param {string} s
* @return {string}
*/
var longestPalindrome = function(s) {
// 我一开始没有初始化好result的值,下面这里是为了应对s长度为1的情况
// 但是后来我发现如果s中没有长度大于1的回文串时,还是没法儿应对,比如:abc
// 所以我把原来的 result='' 改为了 result='s[0]'
// if(s.length === 1) {
// return s;
// }
// 创建一个 s.length × s.length 的二维dp数组,并同时给每个元素赋初始值为true
// 这一步其实就已经包含了 dp 数组的初始化操作,所以后面不用再特意初始化了
// 这里把那些无意义的元素都初始化为 true 是为了方便后面计算某些 dp[i][j]
let dp = Array.from({length: s.length}).map(
() => Array.from({length: s.length}).fill(true)
);
// result用于动态存储最长的回文子串,其初始值应该是单个字符,
// 因为回文串最短也应该有一个字符
let result = s[0];
// 开始遍历s,注意这里 i 的初始值设置为s.length-2是为了避开二维数组的最后一行,
// 而 j 的结束条件设置为 j >= 1,是为了避开二维数组的第一列
for(let i = s.length-2; i >= 0; i--) {
for(let j = s.length-1; j >= 1; j--) {
// 如果i>j,那么就是无意义的,直接跳过本层遍历
if(i > j) {
continue;
}
// 如果s[i]和s[j]相等,且s[i+1]~s[j-1]也为回文,才说明s[i]~s[j]为回文
dp[i][j] = s[i] === s[j] && dp[i+1][j-1];
// 如果s[i]~s[j]为回文,且该回文串的长度大于之前获得的回文串的长度
// 则更新result的值为更长的那个回文串,否则保持原样
result = dp[i][j] && result.length <= j-i+1 ? s.slice(i,j+1) : result;
}
}
// 遍历结束后,result中保存的一定是s中最长的回文子串,将其返回
return result;
};
提交记录
执行结果:通过
177 / 177 个通过测试用例
执行用时:4520 ms, 在所有 JavaScript 提交中击败了5.24%的用户
内存消耗:66.5 MB, 在所有 JavaScript 提交中击败了16.37%的用户
时间:2021/09/22 14:05
其实这道题的动态转移方程比较好推断,关键是要想明白 dp[i][j]
的含义。
或者说,我们得先想明白怎样定义
dp[i][j]
,才能用上动态规划的思路,我觉得这才是所有动态规划类型的题目的关键所在。
官方题解
更新:2021年7月29日18:43:21
因为我考虑到著作权归属问题,所以【官方题解】部分我不再粘贴具体的代码了,可到下方的链接中查看。
更新:2021年9月22日14:07:18
【更新结束】
有关参考
更新:2021年9月22日14:08:16
参考:【算法-LeetCode】53. 最大子序和(动态规划初体验)_赖念安的博客-CSDN博客