马拉车算法
算法的思想是通过在字符串的每个字符之间插入特殊字符(例如"#"),将问题转化为奇数长度的回文串的问题。这样可以统一处理奇数长度和偶数长度的回文串。
时间复杂度:该算法的时间复杂度为O(n),其中n是字符串的长度。马拉车算法只需遍历一次字符串,并且每次遍历都是常数时间操作。
空间复杂度:该算法的空间复杂度为O(n),其中n是字符串的长度。需要额外的空间存储新字符串和p数组。
算法的步骤如下:
- 初始化一个新字符串,将原始字符串进行预处理,插入特殊字符。
- 创建一个数组p,用于存储每个字符作为回文中心时的回文半径。
- 维护两个变量center和rightBoundary,分别表示当前最长回文子串的中心和右边界。
- 遍历新字符串的每个字符,根据不同情况计算回文半径:
- 如果当前字符在rightBoundary的左边,可以利用对称点的回文半径计算当前字符的回文半径。
- 如果当前字符在rightBoundary的右边或恰好处于rightBoundary上,则需要从中心向两边扩散,检查回文长度。
- 更新最长回文子串的中心和右边界。
- 根据p数组和原字符串s,返回最长回文子串。
func main(){
var s string
s="abcabacdab"
ansStr:=longestPalindrome(s)
fmt.Println(ansStr)
}
//5.最长回文子串
func longestPalindrome(s string) string {
// 将字符串转换为新的字符串,用于支持奇偶长度的回文串
ns := initNewString(s)
// 存储每个字符作为回文中心时的回文半径
p := make([]int, len(ns))
// center:当前最长回文子串的中心,rightBoundary:当前最长回文子串的右边界
center, rightBoundary := 0, 0
for i := range ns {
// i_mirror:i关于center的对称点
i_mirror := center*2-i
// 当 i 在 rightBoundary 的左边,可以利用i_mirror的回文半径计算i的回文半径
if i<rightBoundary {
p[i] = min(p[i_mirror], rightBoundary-i)
// 只有当 i_mirror 的回文半径刚好达到 center 回文的左边界时,
// 才需要检查右边界外面的字符是否能够形成新的回文
if p[i_mirror] != rightBoundary-i {
continue
}
}
// 从中心向两边扩散,检查回文长度
for r := p[i]; i-r >= 0 && i+r <= len(ns)-1; r++ {
if ns[i-r] != ns[i+r] {
break
}
p[i] = r
}
// 更新最长回文子串的中心和右边界
if i+p[i] > rightBoundary {
center = i
rightBoundary = i+p[i]
}
}
// 根据p数组和原字符串s返回最长回文子串
return longest(s, p)
}
// 根据p数组和原字符串s返回最长回文子串
func longest(s string, p []int) string {
center, maxRadius := 0, 0
for i, r := range p {
if r > maxRadius {
center = i
maxRadius = r
}
}
start := (center-maxRadius)/2
return s[start:start+maxRadius]
}
// 初始化新字符串
func initNewString(s string) string {
bytes := make([]byte, len(s)*2+3)
bytes[0] = '^'
bytes[len(bytes)-1] = '$'
for i, c := range s {
bytes[i*2+1] = '#'
bytes[i*2+2] = byte(c)
bytes[i*2+3] = '#'
}
return string(bytes)
}
// 返回a和b中的最小值
func min(a, b int) int {
if a < b {
return a
}
return b
}