Manacher

Manacher

题目:求一个字符串中最长的回文串的长度。

如果将字符串化成数组,每遍历一个元素,都要以其为中心判断回文串的长度,时间复杂度比较大。于是就有了Manacher算法,它和KMP算法一样,都是在暴力解的基础上进行改进。

数据处理

对于一个长度为奇数回文字符串,按照暴力法能够得出正确结果。

但是对于长度为偶数的回文字符串,按暴力解法是不能得出正确结果的。

因为长度为偶数的回文字符串的对称轴是虚的,如:aaabba。而长度为奇数的回文字符串的对称轴是字符串中某个具体的元素。

为了解决这个问题,可以将字符串转化,往其中添加辅助字符。如#a#a##a#b#b#a#。这样的话,无论给定字符串的长度是多少,都能正常地进行判断。(用任何字符填充都行,比如说填充了’#’,无论以谁为对称轴,比较的过程中,’#‘只会和’#‘比较,而实际给定的字符不会和’#'比较,不会影响最终结果。)

处理字符串的代码如下

public static char[] change(String str){
    //将字符串转换为数组
    char[] arr = str.toCharArray();
    int len = arr.length;
    //构造一个数组,使用"#"填充
    char[] arr2 = new char[2 * len + 1];
    arr2[0] = '#';
    for(int i = 1, j = 0; j < len; i++, j++){
        arr2[i] = arr[j];
        arr2[++i] = '#';
    }
    return arr2;
}

算法思路

首先引入几个变量(这几个变量的规则是自己定的,不是官方定义。)

  • 回文半径数组:遍历字符串求该数组

    例如aba,以第一个字符a为中心,它自己构成一个回文,记半径为1;以b字符为中心,可构成回文aba,故记半径为2。第三个字符a的回文半径为1。

    则字符串aba对应的回文半径数组为{1,2,1}

  • 最大的回文右边界R(下标):初始值为-1

    例如aba,从头开始遍历,当遍历到第一个字符a时,它自己构成回文,此时回文右边界为字符b的下标,即为1。

    例如sabtbamn,从头开始遍历,当遍历到字符t时,它构成回文abtba,此时回文右边界为字符m的下标,即为6。继续向后遍历到第二个b字符,他自身构成回文,但此时最大回文右边界仍为6。

  • 最大的回文右边界对应的中心C(下标):初始值为-1,随R的值改变而改变。

    例如sabtbamn,从头开始遍历,当遍历到字符t时,它构成回文abtba,此时最大回文右边界为字符m的下标,即为6。而此时中心C就是字符t对应的下标3。继续向后遍历到第二个b字符,最大回文右边界没有变,则C的值也不变,仍为3。

然后介绍数组遍历过程中会出现的集中情况。

  1. 遍历指针i >= R

    如下图,当i=4时,需要以m为中心,向两边扩散判断i=4时的回文半径
    在这里插入图片描述

  2. 遍历指针i < R,且i关于C的对称点i’的回文半径小于(R-i),如下图所示
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-otj2o3Y8-1583991645774)(D:\文档\学习笔记\刷题2\images\Manacher\1583776427661.png)]
    上图中i=8,其关于C的对称点为i’,因为已经遍历到8,所以i’的回文半径已知。且该回文半径小于(R-i),于是i的回文半径可以直接得出来,与**i’**的回文半径相同。

  3. 遍历指针i < R,且i关于C的对称点i’的回文半径大于(R-i),如下图所示
    在这里插入图片描述
    此时i=8,最大的回文右边界R=10,最大的回文右边界对应的回文串为abastsabai关于C的对称点i’的回文半径大于(R-i),此时要判断i的回文半径,需要判断arr[6]是否与arr[10]相等…等后续判断

    对应的代码如下

    public static int function2(String str){
    	//转换数组
    	char[] charArr = change(str);
    	int[] pArr = new int[charArr.length];//charArr[i]的回文半径
    	int C = -1;//回文右边界最大时,此回文的中心
    	int R = -1;//最大的回文右边界
    	int max = Integer.MIN_VALUE;
    	for(int i = 0; i != charArr.length; i++){
    		if(i < R && pArr[2 * C - i] < R - i){//第二种情况
    			pArr[i] = pArr[2 * C - i];
    		}else{
    			if(i >= R){//第一种情况
    				pArr[i] = 1;//pArr[i]的回文半径最小为1,需要接着判断
    			}else{//第三种情况
    				pArr[i] = R - i;//pArr[i]的回文半径最小为R--i,需要接着判断
    			}
    			 //对以上两种情况接着判断,看回文半径是否还能增大
    			while(i + pArr[i] < charArr.length && i  - pArr[i] > -1){
    				if(charArr[i + pArr[i]] == charArr[i - pArr[i]]){
    					pArr[i]++;
    				}else{
    					break;
    				}
    			}
    		}
    		//更新R和C
    		if(i + pArr[i] > R){
    			R = i + pArr[i];
    			C = i;
    		}
    		max = Math.max(max, pArr[i]);
    	}
        return max - 1;
    }
    

    其实还可以使用更简洁的代码,上面的代码比较容易理解

    public static int function2(String str){
    	//转换数组
    	char[] charArr = change(str);
    	int[] pArr = new int[charArr.length];//charArr[i]的回文半径
    	int C = -1;//回文右边界最大时,此回文的中心
    	int R = -1;//最大的回文右边界
    	int max = Integer.MIN_VALUE;
    	for(int i = 0; i != charArr.length; i++){
    		//如果i>=R,先将其回文半径设为1,后续还要继续判断回文半径是否更大
    		//(2 * C - i)为i关于C的对称点i'
    		//如果i<R,且i'的回文半径<(R-i),则i的回文半径等于i'的回文半径,后续的while循环直接退出
    		//如果i<R,且i'的回文半径>=(R-i),暂时将i的回文半径设为(R-i),  有可能i的回文半径会超过R与i之间的距离,后续还要继续判断
    		pArr[i] = i < R ? Math.min(pArr[2 * C - i], R - i) : 1;
    		while(i + pArr[i] < charArr.length && i  - pArr[i] > -1){
    			if(charArr[i + pArr[i]] == charArr[i - pArr[i]]){
    				pArr[i]++;
    			}else{
    				break;
    			}
    		}
    		//更新R和C
    		if(i + pArr[i] > R){
    			R = i + pArr[i];
    			C = i;
    		}
    		max = Math.max(max, pArr[i]);
    	}
    	return max - 1;
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值