这是我参与2022首次更文挑战的第38天,活动详情查看:2022首次更文挑战」
最长回文子串 Longest palindromic substring
LeetCode传送门5. 最长回文子串
题目
给你一个字符串 s
,找到 s
中最长的回文子串。
Given a string s
, return the longest palindromic substring in s
.
Example:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
输入:s = "cbbd"
输出:"bb"
复制代码
Constraints:
- 1 <= s.length <= 1000
- s consist of only digits and English letters.
思考线
解题思路
根据回文中心解题
由于回文字都有一个回文中心,这个回文中心要么是一个字符,要么是2个字符。
我们可以循环s
,然后把s[i]
或者s[i],s[i+1]
当做回文中心来处理。 我们很容易得到以下代码。
function longestPalindrome(s: string): string {
let longest = '';
const len = s.length
for (let i = 0; i < len; i++) {
// 回文中心为i ,或者i,i+1
let j = i
let item = s[i]
let m = 1;
while (j - m >= 0 && j + m < len) {
const b = s[j - m]
const a = s[j + m]
if (b === a) {
item = b + item + a;
m++
} else {
break;
}
}
if (longest.length < item.length) longest = item;
// 为i, i + 1;
if (s[i] === s[i + 1] && i + 1 < len) {
let j = i
let item = s[i] + s[i + 1];
let m = 1;
while (j - m >= 0 && j + m + 1 < len) {
const b = s[j - m]
const a = s[j + m + 1]
if (b === a) {
item = b + item + a;
m++
} else {
break;
}
}
if (longest.length < item.length) longest = item;
}
}
return longest;
};
复制代码
写到这里,我已经暂时想不出如何做到更优解了。于是我去看看别的小伙伴写的答案。思路是一样的思路,不过有个小伙伴的代码封装的比我要好,贴下代码:
function longestPalindrome(s: string): string {
if(s.length < 2){
return s
}
let start:number = 0
let maxLength:number = 1
function fn(left:number,right:number){
while(left >= 0 && right < s.length && s[left] === s[right]){
if(right - left + 1 > maxLength){
maxLength = right -left + 1
start = left
}
left--
right++
}
}
for(let i:number = 0;i<s.length;i++){
fn(i - 1,i + 1)
fn(i,i + 1)
}
return s.substring(start,start + maxLength)
};
复制代码
时间复杂度
: n是字符串长度。 长度为1 和2 的回文中心分别有n
和n-1
个,每个回文中心最多向外扩展 O(n)次。
动态规划思想解题
动归问题,真的是一看就会,一写就废,还是没能写出来。看了题解,记下来心得体会,希望下次能做出来。
关于动态规划我们要思考一下几点
-
找到"状态"和"选择"
-
明确dp数组/函数的定义
-
寻找“状态”之间的关系
-
在这道题中我们的状态是什么呢? 我们求的是最小最长回文子串,在子串中,存在两种状态,是回文子串为
true
,不是回文子串为false
. -
明确dp数组/ 函数的定义。 很明显确定一个字串需要至少两个index
left, right
, 所以我们把dp
定义为一个二维数组,P[left, right], 其中left、right
是子串的首尾点。 -
寻找"状态"之间的关系 假设一个子串为
s[left, right]
(left < right)- 若其本身不是回文子串,那么它不能再构成更长的回文子串。
- 若其本身是回文子串,那么如果还满足
s[left -1] ===s[right+1]
则s[left-1, right +1]
也是回文字符串。
那么我们可以得到动态规划的状态转移方程
上面的转移方程为子串长度大于
2
, 我们还要考虑动态规划中的边界条件,即子串的长度为1
或者2
的情况。- 如果子串长度为1很明显它是一个回文串
- 如果长度为2,只要其字母相同,它也是一个回文串
由此我们可以写出动态规划的边界条件:
有了以上的思路,我们就可以完成动态规划了。最终的答案即为所有P(i,j) = true
中j-i+1
(即子串长度)的最大值。
根据以上思路,我写的代码如下:
function longestPalindrome(s: string): string {
const len = s.length;
let maxLen = 1;
let start = 0;
const dp: boolean[][] = Array.from(new Array(len), () => new Array(len).fill(false));
// 边界条件, 长度为1的都为回文串
for (let i = 0; i < len; i++) {
dp[i][i] = true;
}
for (let L = 2; L <= len; L++) {
for (let i = 0; i < len; i++) {
// 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
const j = L + i - 1;
// 如果右边界越界,就可以退出当前循环
if (j >= len) {
break;
}
if (s[i] != s[j]) {
dp[i][j] = false;
} else {
if (j - i < 3) {
dp[i][j] = true;
} else {
dp[i][j] = dp[i + 1][j - 1];
}
}
// 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
if (dp[i][j] && j - i + 1 > maxLen) {
maxLen = j - i + 1;
start = i;
}
}
}
return s.substring(start, start + maxLen);
};
复制代码
时间复杂度
: 动态规划的状态总数为 。
这就是我对本题的解法,如果有疑问或者更好的解答方式,欢迎留言互动。