Manacher算法——字符串最长回文子串问题O(N)

最长回文子串

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
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

metabit

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值