前言: 本文目的是为了记录并巩固解题思路,希望下次遇到类似的题能想起来=-=
题目如下:
给你一个字符串s,找到 s 中最长的回文子串。
示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd"
输出:"bb"
提示:
- 1 <= s.length <= 1000
- s 仅由数字和英文字母组成
解题:
这道题有很多种解法,暴力、中心扩散、动态规划、manacher算法,是一个经典问题,面试出现频率很高。
首先我们尝试动态规划算法,它的复杂度是O(n²),比中心扩散法(O(n^3))要快一些
一般来说,用动态规划解题之前需要准备4个步骤
- 确定状态
- 子问题:s[i,j]是否是回文子串(i代表左边界,j代表右边界)
- 最后一个状态:true|false,代表是否是回文串
- 构建状态转移方程
-
dp[i , j] = dp[i + 1 ][j - 1]这个状态很容易理解,例如:**'cabac'**这个例子 判断
s[0,4]是否是回文串,需要要基于s[1,3]是否是回文串来决定 如果s[1,3]不是回文串,那s[0,4]必然不是。 所以当s[0]==s[4]时,如果s[1,3]是回文串,那么s[0,4]就可以确定是回文串
- 初始值、边界
- dp[x][x] 都是true,因为它是单个字符,符合回文串规定
- 如果s[i] == s[j] 并且 j - i + 1 <= 3 时,可以直接给dp[i][j]赋值为
true因为当s[i,j]的长度等于0或1时,它就是回文串 当s[i,j]的长度等于2或3时,由于左右相等,所以它也是回文串
-
计算顺序 dp[i,j] 需要基于 dp[i+1][j-1]的状态,
如果此时构建一个二维表格,
j为x轴,i为y轴。会发现,每个格子需要的状态来自于当前格子左下方的格子因为:
x轴 -> j - 1 y轴 -> i + 1
同时,从左上角到右下角的值,都是
true(因为dp[x][x] = true)。所以计算顺序应该从左上角开始,向右、下方更新表的状态
接下来我们来实现一下:
function longestPolinDrome(s) {
const len = s.length;
// 如果长度为0、1,则根据题意,可以直接返回
if (len <= 1) {
return s;
}
// 以空间换时间,创建二维表,缓存所有子问题的最优状态
const dp = new Array(len).fill(false).map(()=>new Array(len).fill(false));
// 初始化状态
for(let i = 0;i < len; i++) {
dp[i][i] = true;
}
let maxLen = 0, begin = -1;
// x轴从1开始填表,因为dp[0,0]已经是true了
for (let j = 1;j < len; j++) {
//y轴上,从上到下填表
for (let i = 0;i < j; i++) {
if (s[j] !== s[i]) {
dp[i][j] = false;
} else {
// 当左边等于右边,并且长度为0,1,2,3时,那么i~j就是个回文串
if (j - i + 1 <= 3 ){
dp[i][j] = true;
}else {
dp[i][j] = dp[i+1][j-1];
}
}
}
if (dp[i][j] && j - i + 1 > maxLen){
maxLen = j - i + 1;
begin = i;
}
}
if(begin >= 0) {
return s.subStr(begin, maxLen);
}
return s[0];
}