这是我参与更文挑战的第16天,活动详情查看: 更文挑战
最长回文子串(题号5)
题目
给你一个字符串 s
,找到 s
中最长的回文子串。
示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd"
输出:"bb"
示例 3:
输入:s = "a"
输出:"a"
示例 4:
输入:s = "ac"
输出:"a"
提示:
1 <= s.length <= 1000
s
仅由数字和英文字母(大写和/或小写)组成
链接
解释
这题啊,这题是棋差一招。
其实差点就可以做出来的,可还是有一个点没想到,最后放弃了。
这题总共有3种解法,笔者想到的是第二种,第一种DP官方说的很详细,但笔者着实看不懂,真的看了很久,思路可以理解,但着代码有点令人迷惑。
第三种解法是数学公式,果断放弃,并不想挑战。
那剩下的就是第二种了,也是笔者一开始就想到了方法。
中心扩展方法,该方法其实很简单,就是从中间往两边找,判断左右是否相等,相等就继续扩展,不相等就GG。
这方法表面上看非常简单,重点就是要考虑中间可能是两个字母的情况,有时候中间是一个字母,有时候是两个字母,这时候就要进行处理了,笔者最开始的想法是判断两个字母的时候是在前还是在后,然后再进行处理啥的,逻辑有点绕,最后还是少考虑了情况导致GG。
其实这里压根就不应该放在一起进行比较,每循环到一个字母的时候,应该同时考虑中心点是一个字母还是两个字母的情况,取最大值作为当前位置的最长回文子串,这就完事了。
自己的答案
无
更好的方法(中心扩展法)
var longestPalindrome = function(s) {
function expandString(left, right) {
while (left >= 0 && right < s.length && s[left] === s[right]) {
--left
++right
}
return right - left - 1
}
var start = 0
end = 0
for (let i = 0; i < s.length; i++) {
var len1 = expandString(i, i)
len2 = expandString(i, i + 1)
len = Math.max(len1, len2)
if (len > end - start) {
start = i - ~~((len - 1) / 2)
end = i + ~~(len / 2)
}
}
return s.substring(start, end + 1)
};
代码比较简单,首先是一个expandString
函数来进行扩展,注意这里的返回值,是right - left - 1
,因为right
和left
会多扩展一次,所以需要减一来获取正确的长度。
循环内部就是正常的逻辑,有len1
和len2
两个长度,第一个很简答,就是从当前位置向两边扩展,len2
就是从当前位置和下一个位置开始,假装中心点是两个开始扩展,由于expandString
函数的内部逻辑,如果两个位置的字母是不一样的,直接GG,这个也不用担心,总之就是试一下的意思。
那么此时拿到两个长度了,取二者之间较大的那个,就是以当前位置为中心点点最大回文子串的长度了。
下面一步也比较关键,这一步会确定截取字符串的起始和结束位置。
其实是一个很巧妙的方法,笔者看了得有10分钟才理解这一步的精华。
这里给start
赋值的时候,取的是~~((len - 1) / 2)
,为什么要这么做?首先考虑len
是奇数的情况,如果是这种情况,其实没出处理的必要,因为~~
代表着向下取整,1.5和1的结果一样的,不用考虑。
那如果len
是偶数呢?这里的结果就凸显出来了,如果是偶数,减1之后,start
会比end
少退出一位,为什么要这么做?因为如果是偶数的话,意味着中心点是两个字母,但index
是从第一个字母开始的,所以左侧的扩展需要比右侧少一个,这样扩展的个数就能对齐了。
说起来还是可能比较迷幻,笔者的表述能力也就到此为止了,尽力局。
如果还是看不懂可以打印点东西试试,考虑中心点是一个还是两个情况。
更好的方法(DP)
讲真,DP没看懂,但笔者还是拿JavaScript翻译了下官方答案,感兴趣的同学可以看看👇:
var longestPalindrome = function(s) {
var len = s.length
if (len < 2) return len
var maxLen = 1
begin = 0
dp = Array.from({length: len}, () => new Array(len))
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++) {
var 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]
}
}
if (dp[i][j] && j - i + 1 > maxLen) {
maxLen = j - i + 1;
begin = i
}
}
}
return s.substring(begin, begin + maxLen)
};
更好的方法(Manacher)
这方法就是天书了,详见官方答案👉:这里。
PS:想查看往期文章和题目可以点击下面的链接:
这里是按照日期分类的👇
经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇
有兴趣的也可以看看我的个人主页👇