算法训练营【5】Manacher算法及其扩展

Manacher算法

时间复杂度O(N) 快速求出字符串的最长回文子串的长度

abccba 最长为6

1124211 最长为7

暴力解法

以每一个字符为中心向两边扩,但是这种办法只能保证奇数的解决,无法计算到偶数的

解决方法为,在每两个字符中间加一个字符,头尾也加一个字符,

然后再以每个字符为中心往两边扩充,最后得出的结果都/2 (向下取整)最大的那个数字就是最长回文字串的长度了

时间复杂度为n方

最坏情况为每个字符都相同,前半部的字符需要找到左边界,后半部分字符需要找到右边界

Manacher 算法

回文半径

​ 数一侧

回文直径

​ 两次都算

回文半径数组

​ arr

到目前为止的回文最右边界(记做R) 与 中心 (记做C)

​ 回文最右边界为当前遍历过的字符的最右边的字符位置,中心的意思是当前最右边界下的中心位置的字符

​ 1x1x3x1x1 (x为辅助字符)

​ 若当前字符为1 ,位置为0,则最右字符为位置0,中心为也为0

算法步骤

​ index 在 R外,暴力计算最长回文字串

​ index在R内:

​ index关于C位置的对称位置 index2 ,

​ 求index 的最长回文字串需要看index2,

​ index2的最长回文串有三种情况

​ 最长回文串在, 【r2-------C-------R】内

​ arr【index】=arr【index2】

​ 最长回文串在, 【r2-------C-------R】外,超出一部分

​ arr【index】= R-index

​ 最长回文串在, 【r2-------C-------R】内,刚好压线

​ arr【index】= arr【R-index】+ ?

​ ?是因为不知道再往右扩充时是否还能构成回文串

public static int manacher(String s) {
    
    
		if (s == null || s.length() == 0) {
    
    
			return 0;
		}
		// "12132" -> "#1#2#1#3#2#"
		char[] str = manacherString(s);
		// 回文半径的大小
		int[] pArr = new int[str.length];
		int C = -1;
		// 讲述中:R代表最右的扩成功的位置
		// coding:最右的扩成功位置的,再下一个位置
		int R = -1;
		int max = Integer.MIN_VALUE;
		for (int i = 0; i < str.length; i++) {
    
     // 0 1 2
			// R第一个违规的位置,
      // i>= R 为 i在R外,至少不用验证的区域为1 
			// i位置扩出来的答案,i位置扩的区域,至少是多大。
      // 2*c-i。是i的对称点
      //
      
			pArr[i] = R > i ? Math.min(pArr[2 * C - i], R - i) : 1;
      
      // 不用验证的部分结束了,开始计算左右两边是否能够再扩充
			while (i + pArr[i] < str.length && i - pArr[i] > -1) {
    
    
				if (str[i + pArr[i]] == str[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]);
		}
  // 因为求的是半径,所以是max-1,若求的是直径,那就是 max/2
		return max - 1;
	}
 
	public static char[] manacherString(String str) {
    
    
		char[] charArr = str.toCharArray();
		char[] res = new char[str.length() * 2 + 1];
		int index = 0;
		for (int i = 0; i != res.length; i++) {
    
    
			res[i] = (i & 1) == 0 ? '#' : charArr[index++];
		}
		return res;
	}

添加字符变成回文串

在一个字符串str中,在末尾添加多少个字符,可以使得整个字符串为回文字符串?


题目在求,必须包含最后一个字符的情况下,最长回文串有多长

当最后一个回文子串包含最后一个字符时,

将最后一个字符前面 没有被回文子串包住的字符,逆序填到字符串末尾

abc 12321

abc 【12321】 cba

查找最左的,回文半径能把最后一个字符包住的位置,

然后找到左边界,把左边界到字符开头的字符逆序补充到字符串尾部

public static String shortestEnd(String s) {
    
    
		if (s == null || s.length() == 0) {
    
    
			return null;
		}
		char[] str = manacherString(s);
		int[] pArr = new int[str.length];
		int C = -1;
		int R = -1;
		int maxContainsEnd = -1;
		for (int i = 0; i != str.length; i++) {
    
    
			pArr[i] = R > i ? Math.min(pArr[2 * C - i], R - i) : 1;
			while (i + pArr[i] < str.length && 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 == str.length) {
    
    
				maxContainsEnd = pArr[i];
				break;
			}
		}
  //maxContainsEnd - 1  为包含最后一个字符的最长回文长度
		char[] res = new char[s.length() - (maxContainsEnd - 1];
		for (int i = 0; i < res.length; i++) {
    
    
			res[res.length - 1 - i] = str[i * 2 + 1];
		}
		return String.valueOf(res);
	}
 
	public static char[] manacherString(String str) {
    
    
		char[] charArr = str.toCharArray();
		char[] res = new char[str.length() * 2 + 1];
		int index = 0;
		for (int i = 0; i != res.length; i++) {
    
    
			res[i] = (i & 1) == 0 ? '#' : charArr[index++];
		}
		return res;
	}

猜你喜欢

转载自blog.csdn.net/qq_41852212/article/details/121472125