题目
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例
示例 1:输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:输入: "cbbd"
输出: "bb"
解决方案
方法一、动态规划
我们首先观察如何避免在验证回文时进行不必要的重复计算。考虑“ababa” 这个示例。如果我们已经知道“bab” 是回文,那么很明显,ababa” 一定是回文,因为它的左首字母和右尾字母是相同的。我们给出 P(i,j) 的定义如下:
因此,P(i, j) = ( P(i+1, j-1) and S(i) == S(j) )
基本示例如下:
P(i, i) = trueP(i,i)=true
P(i, i+1) = ( S(i) == S(i+1) )
这产生了一个直观的动态规划解法,我们首先初始化一字母和二字母的回文,然后找到所有三字母回文,并依此类推…复杂度分析时间复杂度:O(n^2), 这里给出我们的运行时间复杂度为 O(n^2) 。空间复杂度:O(n^2), 该方法使用 O(n^2) 的空间来存储表。
方法二:中心扩展算法
事实上只需使用恒定的空间,我们就可以在 O(n^2) 的时间内解决这个问题。我们观察到回文中心的两侧互为镜像。因此,回文可以从它的中心展开,并且只有 2n - 1 个这样的中心。你可能会问,为什么会是 2n - 1个,而不是 n个中心?原因在于所含字母数为偶数的回文的中心可以处于两字母之间“abba” 的中心在两个‘b’之间)。
func longestPalindrome(s string) string {
// odd number
n := len(s)
if n == 0 {
return ""
}
if n == 1 {
return s
}
var res1, res2 int
var tmp1, tmp2 int
var str1, str2 string
for i := 0; i < n-1; i++ {
tmp1 = 1
l1 := i - 1
r1 := i + 1
for {
// fmt.Println("##i=%d", i)
if l1 >= 0 && r1 <= n-1 {
// fmt.Println("s[l1]=%v, s[i]=%v, s[r1]=%v", string(s[l1]), string(s[i]), string(s[r1]))
if s[l1] == s[r1] {
tmp1 += 2
l1--
r1++
} else {
break
}
} else {
break
}
}
if tmp1 > res1 {
str1 = s[l1+1 : r1]
res1 = tmp1
}
// fmt.Printf("##str1=%s\n\n", str1)
}
// even number
for j := 0; j < n-1; j++ {
tmp2 = 0
l2 := j
r2 := j + 1
for {
// fmt.Println("##j=%d", j)
if l2 >= 0 && r2 <= n-1 {
// fmt.Printf("l2=%d, r2=%d\n", l2, r2)
// fmt.Println("s[l2]=%v, s[r2]=%v", string(s[l2]), string(s[r2]))
if s[l2] == s[r2] {
tmp2 += 2
l2--
r2++
} else {
break
}
} else {
break
}
}
if tmp2 > res2 {
str2 = s[l2+1 : r2]
res2 = tmp2
}
// fmt.Printf("##str2=%s\n\n", str2)
}
// fmt.Printf("##res1=%d, res2=%d\n\n", res1, res2)
if res2 > res1 {
return str2
}
return str1
}
复杂度分析
- 时间复杂度:O(n^2), 由于围绕中心来扩展回文会耗去 O(n) 的时间,所以总的复杂度为 O(n^2)。
- 空间复杂度:O(1)。