找到最长回文字符串 - Manacher's Algorithm

记得刚开始学习计算机的时候,除了输出星星就是找到最长回文字符串这样的问题, 开始以为最长回文串问题很简单,但是经过多年的学习再回头看的时候发现,它并不简单,今天就给它解个密。


开始我们先来看一个时间复杂度差的算法。 


1. 时间复杂度为 n^3.    空间复杂度为 1.  这个算法基本上是最简单的了,最好理解的。

/**
	 * 使用iteration
	 * 循环三次
	 * 	 	第一次(最外边循环 : 从最长的长度开始,循环长度,每次减1
	 * 		第二次 (中间循环): 对每个长度,循环最长字符串的起始点,起点从0每次加1
	 * 		第三次 (最内循环): 对每个长度每个起点,对左右进行比较,每次左+1,右-1
	 * 
	 * 
	 * 
	 * 
	 * */
	public static String longestP (String S) {
		if (S.length() < 2 || S == null) { return S; }
		int length = S.length();
		int left = 0;
		int right = 0;
		while (length >= 0) {
			
			for (int i = 0; i + length - 1 < S.length(); i++) {
				left = i;
				right = i + length - 1;
				
					while (left < right) {
						if (S.charAt(left) == S.charAt(right)) {
							left ++;
							right --;
							continue;
						} else {
							break;
						}
					}
					
					if (left >= right) { return S.substring(i, i+ length); }
			}
			
			length --;
		}
		
		return "";
	}


2. Manacher's Algorithm - 时间复杂度为 N ( 应该大于N 小于 N ^ 2)

这个算法需要一个预处理主要的三个变量


预处理: 将输入字符串的各个字符使用特殊字符进行 分割,比如 #。   e.g.  input: abba 处理: #a#b#b#a#

三个主要变量: 

1. 一个和预处理之后大小一样的 int 数组 rad[] - 用来存储以当前字符为中心的最长子回文字符串的半径。

2. 一个整形变量来存储- 我们遍历过的所有子回文字符串能触及到的最右边的位置的maxRight。

3. 另一个整形变量来存储 - 2.中maxRight所对应的 子回文字符串的中心点的位置pos。


每次遍历从以当前字符为中心向两头扩展,扩展的半径是多少?

这就是个问题。。。


解决办法, 通过对比 i 和 maxRight, 当i < maxRight 的时候, 判断 maxRight - i 的大小 和  一个点 j ( 这个点是 i 以 pos为对称的  对称点 )的最长回文半径的大小(存储在rad 中). 

使用较小值作为半径, 从当前点开始进行两头扩张。每次扩张成功之后半径+1。


public static String Manacher (String S) {
		if (S.length() < 2 || S == null) { return S; }
		
		//construct new string
		//basically, insert one # to the String S,
		//make sure each character is surrounded by #
		StringBuilder newS = new StringBuilder();
		newS.append("#");
		
		int start = 0;
		while (start < S.length()) {
			newS.append(S.charAt(start));
			newS.append("#");
			
			start ++;
		}
		
		int [] rad = new int[newS.length()];	//to store the radius of a string with pivot of current node
		int maxRight = -1;	//most right we can touch.
		int pos = -1;		//the position for which node that can touch the most right.
		
		for (int i = 0; i < newS.length(); i++) {
			int r = 1; 	//radius 
			
			if (i <= maxRight) {
				//since i is less than the max right,
				//so we can compare the radius of the node which is symmetric with pos and centered on current node
				r = Math.min(rad[2*pos - i], maxRight - i);
			}
			
			//extend the string, compare with the i - r(left) and i + r (right)
			//if they equal, so that radius ++
			while (i - r >= 0 && i + r < newS.length() 
					&& newS.charAt(i-r) == newS.charAt(i+r)) {
				r++;
			}

			//if i + r -1 is greater than the most right that the most right we can touch.
			//we have to update the max right and the position of max right
			if (i + r - 1 > maxRight) {
				maxRight = i + r - 1;
				pos = i;
			}
			
			rad[i] = r;
		}
		int MaxR = 0;
		int pivot = 0;
		for (int i = 0; i < rad.length; i++) {
			if (rad[i] > MaxR) {
				MaxR = rad[i];
				pivot = i;
			}
			
		}
		return newS.substring(pivot - MaxR + 1, pivot + MaxR).replace("#", "");
	}


有问题欢迎指出。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值