Manacher算法解决最长回文串问题

        如果求一个字符串,求字符串中最长连续回文子串问题,如果用暴力求解的话会使得时间复杂度为O(n^2),因为暴力方法在以不同字符为中心往两边扩的时候,每次都是往两边扩,那么其中有好多重复扩的部分。而本文讲解的manacher算法就是使得前面将前面扩的部分备份,后面扩的时候便于省去不需要重复扩的部分,从而降低时间复杂度,使得时间复杂度大约为O(n)。首先先说明manacher算法的三个重要的变量,pArr数组,用于存储以每一个位置为中心的最大回文半径;right表示右边界,即当前位置之前扩到字符的最右边界;index表示扩到right时对应的字符位置。在进行算法之前,需要对算法进行预处理,为了避免奇偶性,所以在该字符串中穿插一个标记字符,比如'#',在字符串首尾以及每两个字符之间插入一个标记字符。

        然后进行讨论manacher的算法思路:

1.如果当前位置i在right之外,则不断往两边扩,扩到不是回文为止。

2.如果i在right之内,则需要讨论几种情况:这时候需要找到i关于index的对称位置:2*index-i,并判断pArr[2*index-i]与right-i的关系,如果前者小于后者,那么pArr[i]=pArr[2*index-i],并且不需要再往外扩,只需要接着判断下一个i即可;还有种情况便是pArr[2*index-i]大于等于right-i,这时候需要截取的是right-i的部分,因为right之后的字符是未知的,所以需要以i为中心,并从半径right-i开始往两边扩,所有情况扩的过程不仅要满足字符对称相等,还需要满足边界问题。假设扩的半径为r,则需要满足i-r>=0同时满足i+r<length.我们首先来分析一下比较复杂的情况,就是i关于index对称位置的左边界在index为中心的左边界的左边的情况,如下图所示:


        如上图所示的字符串的当前index和right如图所示,假设目前的i位置如图所示,该位置的对称位置i'如图所示,图中可知i'的左边界超出了index的左边界,因此不能直接把i'的回文半径给i位置使用,因为i位置的right之后是位置的。但是可以保证的是2*i-right-->i-->right部分是回文串,根据这段关于index对称到左边对应的是i'为中心的最大回文串的子回文串,所以可以将开始扩的位置定位到right+1开始。所以可以看出,当i<right的时候,可以总结为在pArr[2*index-i]和pArr[index]+index-i之间取较小值即可。假设i位置的半径最终为r,如果i+r-1>right的时候需要更新right的index值。注意r是局部变量,每遍历一个i值时,r都从1开始。

具体代码如下所示:

import java.util.*;
public class Manacher {

	public static int manacher(String str)
	{
		//首先进行预处理。
		int n=str.length();
		StringBuffer sb=new StringBuffer();
		sb.append("#");
		for(int i=0;i<n;i++)
		{
			sb.append(str.charAt(i)).append("#");
		}
		int[] pArr=new int[2*n+1];
		int right=-1;//为了后续更容易判断,从-1开始。
		int index=-1;
		for(int i=0;i<sb.length();i++)//开始逐个遍历求以各个字符为中心的最大回文半径。
		{
			int r=1;//每个字符最少半径为1.
			if(i<=right)//判断是够在right之内,如果是则寻找开始扩充的边界,如果不是则直接从当前字符往两边扩充。
			{
				r=Math.min(pArr[2*index-i], pArr[index]+index-i);//需要取小值,因为对称点回文串左边界可能在index左边界之外。对于当前点来说它已知的边界不能超过right。
			}
			//开始扩充,扩充的时候需要在保证相等的时候注意边界。
			while(i-r>=0&&i+r<sb.length()&&sb.charAt(i+r)==sb.charAt(i-r))
				r++;
			pArr[i]=r;
			//扩充完之后需要检查是否需要更新index和right
			if(i+r-1>right)
			{
				right=i+r-1;
				index=i;
			}
			
		}
		StringBuffer sb1=new StringBuffer();
		int maxnum=1;
		int k=0;
		for(int i=0;i<pArr.length;i++)
		{
			if(pArr[i]>maxnum)
			{
				maxnum=pArr[i];
				k=i;
			}
		}
		System.out.println(k);
		for(int i=k-maxnum+1;i<k+maxnum;i++)
		{
			if(sb.charAt(i)!='#')
				sb1.append(sb.charAt(i));
		}
		
		System.out.println(sb1.toString());
		return sb1.length();
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		String str="baabbaaa";
		System.out.println(manacher(str));

	}

}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值