Manacher Algorithm讲解

首先我们解决下奇数和偶数的问题,在每个字符间插入"#",并且为了使得扩展的过程中,到边界后自动结束,在两端分别插入 “^” 和 “$”,两个不可能在字符串中出现的字符,这样中心扩展的时候,判断两端字符是否相等的时候,如果到了边界就一定会不相等,从而出了循环。经过处理,字符串的长度永远都是奇数了。
首先我们用一个数组 P 保存从中心扩展的最大个数,而它刚好也是去掉 “#” 的原字符串的总长度。例如下图中下标是 6 的地方。可以看到 P[ 6 ] 等于 5,所以它是从左边扩展 5 个字符,相应的右边也是扩展 5 个字符,也就是 “#c#b#c#b#c#”。而去掉 # 恢复到原来的字符串,变成 “cbcbc”,它的长度刚好也就是 5。
求原字符串下标用 P 的下标 i 减去 P [ i ],再除以 2 ,就是原字符串的开头下标了。例如我们找到 P[ i ] 的最大值为 5 ,也就是回文串的最大长度是 5 ,对应的下标是 6 ,所以原字符串的开头下标是 (6 - 5 )/ 2 = 0 。所以我们只需要返回原字符串的第 0 到 第 (5 - 1)位就可以了。求每个 P [ i ]接下来是算法的关键了,它充分利用了回文串的对称性。我们用 C 表示回文串的中心,用 R 表示回文串的右边半径坐标,所以 R = C + P[ C ] 。C 和 R 所对应的回文串是当前循环中 R 最靠右的回文串。让我们考虑求 P [ i ] 的时候,如下图。用 i_mirror 表示当前需要求的第 i 个字符关于 C 对应的下标。我们现在要求 P [ i ], 如果是用中心扩展法charAt(i+p[i]+1) == charAt(i-p[i]-1),那就向两边扩展比对就行了。但是我们其实可以利用回文串 C 的对称性。i 关于 C 的对称点是 i_mirror ,P [ i_mirror ] = 3,所以 P [ i ] 也等于 3 。

另:
对于奇数长度的回文字符串,其中间点为中间的字符;对于偶数长度的回文字符串,其中间点在中间两个字符之间。现假设我们要暴力找一个字符串所有的子字符串,则其所有可能成为子字符串中间点的位置如下(字符之间的位置用竖线表示):
在这里插入图片描述
例如position=2处,左边是a,右边是b,p[2]=0;position=3处,以其为中心的最长回文字符串为’aba’,所以p[3]=3。另一方面,如果把竖线也当作一个字符,则很容易证明p还有另一种解释:以该position为中心的LPS,向两边延展的步数。例如position=3处,从b向左向右各3步,得到’|a|b|a|’,为回文串,因此p[3]=3;position=6处,从|向左向右各6步,得到’|a|b|a|a|b|a|’,为回文串,因此p[6]=6。故p[i]即为回文字符串长度

public String preProcess(String s) {
	int n = s.size();
	//处理S字符串使得ret = ^#...#$
	if(n==0)
	return "^$";
	int []p = new int[n];

	string ret = "^";
	for(int i = 0; i < n: i++)
	{
		ret = "#" + s.charAt(i);
	}
    ret += "#$";
	return ret;
}
    
// 马拉车算法
public String longestPalindrome2(String s) {
    String T = preProcess(s);
    int n = T.length();
    int[] P = new int[n];
    int C = 0, R = 0;
	for(int i = 1; i < n-1: i++)
	{
		int i_mrrior = 2*C-i;//i_mrrior关于c与i对称(c=(i+i_mrrior)/2)
		if(R>i)
		{
            p[i] = math.min(R-i,p[i_mrrior]); //防止溢出
		}
		else
		{
			p[i] = 0;
		}

        while(charAt(i+p[i]+1) == charAt(i-p[i]-1))//中心扩展法,因为p[i]+i就已经表示回文串最右边半径坐标了,此时再往右移一步,判断下一个点是否相等
		{
            p[i]++;
		}
        
		//更新C和R

		if(R<p[i]+i)
		{
           R = p[i]+i;
		   C = i;
		}

	}
    //找出P的最大值
    int maxLen = 0;  //p[c]
    int centerIndex = 0;//c
	for(int i = 1; i < n - 1; i++)
	{
		if(maxLen<p[i])
		{
			maxLen = p[i]
			centerline = i;
		}
	}
	int begin = (centerline - maxLen)/2;
	return s.substring(begin,begin+maxLen);

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值