package Manacher
import (
"math"
"math/rand"
"testing"
"time"
)
// Manacher算法 解决最长回文子串问题
// abcba abba 长度为奇数和长度为偶数的回文都可以
// 可以认为有一个轴 在实的位置或虚的位置
// Manacher 解决在一个字符串中最长回文子串有多大这个问题
// 最长回文子序列是动态规划的问题,子串必须是连续的
// 回文 DNA是一些序列,可以认为是字符串,DNA的一些基因片段具有回文属性,能找到一些生理学的意义
// 暴力解,每个位置i 求以i为中心的回文串长度,不一定找的完整,因为虚轴被忽略了,所以只能找到长度为奇数的回文串
// 优化: 在每个字符中间插入同一个字符
/*
abc121def
11112....
a#b#c#1#2#1#d#e#f
偶数的长度会插入奇数个占位符,最终字符串奇数个
奇数的长度会插入偶数个占位符,最终字符串奇数个
任何一个位置做统计,除2,就是原字符串的回文的情况
如果原始串中包含#,无所谓奇偶略过性质
比对的时候,都是实轴碰实轴,虚轴碰虚轴,没有虚轴碰实轴的时候
暴力解复杂度: O(N^2)
Manacher O(N) 复杂度
从左往右建立加速数组
Manacher 在线性时间内解决最长回文子串问题
0.回文半径,回文直径,回文区域,回文覆盖,回文左边界,回文右边界
4 7 ..略
f#1#a#1#s
1.回文半径数组 pArray[], 每个位置求一个答案,保留下来, 用来加速 求i位置的答案,用i-1 的位置快速求解
2.回文最右边界, 一个变量 R -1 一开始是 -1, R更新C就更新
3.中心 一个变量 C -1 一开始是 -1 取得R时候的中心点,获得更右的边界,就更新C
算法主过程
pArr R C
*以每个位置向左右两边扩
以i为中心向左右两边扩
1. i在R外,没有优化,暴力比对 i -1 i i + 1 | i - 2 i i + 2
2. i在R内,(i = R时也算), [i' C i]
以对称点的情况,向西划分
*/
func manacher(s string) int {
if len(s) == 0 {
return 0
}
str := manacherString(s) // "12132" -> "#1#2#1#3#2#"
pArr := make([]int,len(str))
C := -1
// 讲述中:R代表最右的扩成功的位置
// coding:最右的扩成功位置的,再下一个位置
R := -1
max := math.MinInt
for i := 0; i < len(str); i++ { // 0 1 2
// R第一个违规的位置,i>= R
// i位置扩出来的答案,i位置扩的区域,至少是多大。
pArr[i] = 1
if R > i {
pArr[i] = Min(pArr[2*C - i], R - i)
}
for i + pArr[i] < len(str) && i - pArr[i] > -1 {
if str[i + pArr[i]] == str[i - pArr[i]] {
pArr[i]++
}else {
break
}
}
if i + pArr[i] > R {
R = i + pArr[i]
C = i
}
max = Max(max,pArr[i])
}
return max - 1
}
func manacherString(str string) []byte { // todo 处理中文可能会发生乱码
res := make([]byte,len(str) * 2 + 1)
index := 0
for i := 0; i != len(res); i++ {
if (i & 1) == 0 {
res[i] = '#'
}else{
res[i] = str[index]
index++
}
}
return res
}
func Min(a, b int) int {
if a > b {
return b
}
return a
}
func Max(a, b int) int {
if a < b {
return b
}
return a
}
func right(s string) int {
if s == "" || len(s) == 0 {
return 0
}
str := manacherString(s)
max := 0
for i := 0; i < len(str); i++ {
L := i - 1
R := i + 1
for L >= 0 && R < len(str) && str[L] == str[R] {
L--
R++
}
max = Max(max, R - L -1)
}
return max / 2
}
func getRandomString( possibilities, size int) string {
rand.Seed(time.Now().UnixNano())
ans := make([]byte,rand.Int() % size +1)
for i := 0; i < len(ans); i++ {
ans[i] = byte(rand.Int() %possibilities + 'a')
}
return string(ans)
}
func TestManacher(t *testing.T) { // todo 处理中文可能会发生乱码
possibilities := 50
strSize := 200
testTimes := 500000
t.Log("test begin")
for i := 0; i < testTimes; i++ {
str := getRandomString(possibilities,strSize)
if a, b := manacher(str), right(str); a != b {
t.Fatal(str,a,b)
return
}
}
t.Log("test finish")
}
/*
给你一个字符串,让他整体变成回文串,只能在字符串后边添加字符串,保证添加的字符串最短,返回添加介个字符
abc12321
此题求的是最后一个字符开头回文字符串有多长,把前边剩余的逆序过来 拼接在后边就可以
[abc]12321 [[cba]]
求最左的,能把最后一个字符包住的回文串,然后剩下的逆序
*/
func shortestEnd(s string) string { // todo 处理中文可能会发生乱码
if len(s) == 0 {
}
str := manacherString(s)
pArr := make([]int,len(str))
C := -1
R := -1
maxContainsEnd := -1
for i := 0; i != len(str); i++ {
pArr[i] = 1
if R > i {
pArr[i] = Min(pArr[2 * C - i], R - i)
}
for i + pArr[i] < len(str) && i - pArr[i] > -1 {
if str[i+ pArr[i]] == str[i-pArr[i]] {
pArr[i]++
}else {
break
}
}
if i + pArr[i] > R {
R = i + pArr[i]
C = i
}
if R == len(s) {
maxContainsEnd = pArr[i] // 回文半径
break
}
}
res := make([]byte,len(str) - maxContainsEnd + 1)
for i := 0; i < len(res); i++{
res[len(res)- i -1] = str[i *2 + 1]
}
return string(res)
}
https://leetcode-cn.com/problems/longest-palindromic-substring/
func longestPalindrome(s string) string {
start, end := 0, -1
t := "#"
for i := 0; i < len(s); i++ {
t += string(s[i]) + "#"
}
t += "#"
s = t
arm_len := []int{}
right, j := -1, -1
for i := 0; i < len(s); i++ {
var cur_arm_len int
if right >= i {
i_sym := j * 2 - i
min_arm_len := min(arm_len[i_sym], right-i)
cur_arm_len = expand(s, i-min_arm_len, i+min_arm_len)
} else {
cur_arm_len = expand(s, i, i)
}
arm_len = append(arm_len, cur_arm_len)
if i + cur_arm_len > right {
j = i
right = i + cur_arm_len
}
if cur_arm_len * 2 + 1 > end - start {
start = i - cur_arm_len
end = i + cur_arm_len
}
}
ans := ""
for i := start; i <= end; i++ {
if s[i] != '#' {
ans += string(s[i])
}
}
return ans
}
func expand(s string, left, right int) int {
for ; left >= 0 && right < len(s) && s[left] == s[right]; left, right = left-1, right+1 { }
return (right - left - 2) / 2
}
func min(x, y int) int {
if x < y {
return x
}
return y
}