【算法-LeetCode】5. 最长回文子串(动态规划)

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 仅由数字和英文字母(大写和/或小写)组成

我的题解(动态规划)

有关动态规划的思路总结,之前写过一个相关的题解博客:

参考:【算法-LeetCode】53. 最大子序和(动态规划初体验)_赖念安的博客-CSDN博客

里面也有我写的其他动态规划题解,一些通用的步骤都是一样的,也可以作为参考,进行对照思考。

那就按照上面提到的那几个步骤开始分解:

①确定 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数组手动填充结果

于是状态转移方程就可以很容易写出来了: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

参考:最长回文子串 - 最长回文子串 - 力扣(LeetCode)

【更新结束】

有关参考

更新:2021年9月22日14:08:16
参考:【算法-LeetCode】53. 最大子序和(动态规划初体验)_赖念安的博客-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值