这道题其实每次看题目列表都会看到,然后还每次似懂非懂,也看了很多次解法了,但是就是看一次忘一次。
今天趁着明天周末可以不用早起,准备写一两道,又看到了这道题,尝试按照记忆写一写。
题目
最长回文子串
这里需要注意的是子串,而不是子序列,子串必须是连续的,子序列没有要求连续(516. 最长回文子序列)
解法
- 暴力解法:
所谓暴力,无非就是穷举,然后判断每个子串是否是回文,然后和记录的长度做比较,判断是否更新。
首先就要穷举出所有子串:双循环,外层表示子串的起点位置,内层表示子串的终点位置。
fun longestPalindrome(s: String): String {
if(s.length<2) return s
var maxLen=0
var start=0
for (i in 0 until s.length){
for(j in i until s.length){
if(check(s,i,j)&&j-i+1>maxLen){
maxLen=j-i+1
start=i
}
}
}
return s.substring(start,start+maxLen)
}
private fun check(s:String, i:Int, j:Int):Boolean{
var start=i
var end=j
while(start<=end){
if(s[start]!=s[end])
return false
start++
end--
}
return true
}
哈哈,执行耗时可知速度是真的慢
- 中心扩散法
暴力解法判断一个子串需要两次遍历确定每个子串,再去判断,耗时的主要原因就在这。
中心扩散法是采用反向的思维,先确定回文中心,再求得以此中心的最长回文子串长度。
fun longestPalindrome(s: String): String {
if(s.length<2) return s
var end=0
var start=0
for (i in 0 until s.length){
val len=Math.max(getLen(s,i,i),getLen(s,i,i+1))
if(len>end-start) {
end = i+len/2
start = i-(len-1)/2
}
}
return s.substring(start,end+1)
}
/**
* start end中间字母数量
* 实际长度是 返回值+1
* 减一是为了好统一处理奇数个数和偶数个数
*/
private fun getLen(s:String, i:Int, j:Int):Int{
var start=i
var end=j
while(start>=0&&end<s.length){
if(s[start]!=s[end])
return end-start-1
start--
end++
}
return end-start-1
}
果然快多了。。。
注意点:
getLen返回的end-start-1/
一般情况下如果是中心单数对称如aba,那么
start=i+len/2
end=i-len/2
这时候len是实际长度还是长度-1,实际上没区别(3/2==2/2)
如果是双数对称如abba,这个时候i指向第一个b,所以start是靠左的,计算起始位置要少减一位
这时候如果len是实际长度:
end=i+len/2
start=i-len/2+1
所以通过返回长度-1的方式消除这两种情况的差异
- 马拉车算法
待续。。。