标签:两种 bsp print turn 部分 特殊 不能 计算 ram
题目描述
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:
输入: "cbbd"
输出: "bb"
方法一暴力求解: 定义一个n*n维的数组flage,flage[i][j] 表示以i开始,以j结束的回文串的长度,如果Si,,,,,,Sj是回文串,那么flage[i][j]就是长度,否则值为0。然后取出flage数组中元素最大的值并返回相应的回文子串。此方法时间复杂度为O(n^3), 空间复杂度可以为O(1),(每次只保存最优结果数据,从而不需要用数组)---复杂度太大会超时
/** * 暴力方法 * 假设flage[i][j]表示以i开始,以j结束的回文串的长度。 * 计算出所有以i开始以j结束回文串的长度大小,不是回文串的设置为0 * * @param s * @return */ public String longestPalindrome1(String s) { // a[i][j] 表示以i开始, 以j结束的串, 如果是回文串保存长度,不是直接为0 int max = 1, x = 0, y = 0; int len = s.length(); int[][] flage = new int[len][len]; for (int i = 0; i < len; i++) flage[i][i] = 1; for (int i = 0; i < len; i++) { for (int j = i + 1; j < len; j++) { flage[i][j] = TheLengthOfStr(s.substring(i, j + 1)); if (max < flage[i][j]) { max = flage[i][j]; x = i; y = j; } } } return s.substring(x, y + 1); }
方法二:动态规划 本质上是暴力方法的改进版本,
它利用数组P[i][j]保存Si到Sj是否是回文串,然后利用状态转移方程 P[i][j]={P[i+1][j-1]&&Si==Sj}, 初始化条件为P[i][i]=1, P[i][i+1]=Si==S(i+1)
先从找两个元素的回文串,再找三个元素的回文串,接着找四个元素的回文串,一直这样下去,直到k个元素的回文串。 时间复杂度为O(n^2), 空间复杂度为O(n^2)
public String longestPalindrome2(String s) { // 先初始化 一位 char[] cSubstr = s.toCharArray(); int maxValue = 0; int low = 0, high = 0; int len = s.length(); boolean[][] P = new boolean[len][len]; for (int i = 0; i < len; i++) { P[i][i] = true; } // 从2个元素到k个元素 P[j-k-1][j-1] boolean isFlage = false; for (int k = 1; k < len; k++) { isFlage = false; for (int j = k; j < len; j++) { P[j - k][j] = cSubstr[j - k] == cSubstr[j]&&(k==1||P[j-k+1][j-1]); if (P[j - k][j]&&!isFlage) { maxValue = k; low = j - k; high = j; isFlage = true; } } } return s.substring(low, high + 1); }
方法三:中心扩展法,其实就是枚举所有可能的中心点,从而来获得最大的回文串长度。 由于可能存在奇数或者偶数个回文串长度,所以中心点的选择需要考虑上述两种情况。
当回文串长度为偶数时,有n-1种可能的中心点;当回文串长度为奇数时,存在n种可能的中心点。也就是说总共存在2n-1种中心点情况。时间复杂度为O(n^2)
public String longestPalindrome3(String s) { int len = s.length(); if(len<=1) return s;
// 表示最优回文串对应的上下界, 界限包含在内 int start = 0, end = 0; for (int i = 0; i < len; i++) {
// 奇数情况 int len1 = theLengthOfSubStr(s, i, i, len);
// 偶数情况 int len2 = theLengthOfSubStr(s, i, i + 1, len); int le = Math.max(len1, len2); if (le > start - end) { // 如果新的回文串长度更长,修改回文串对应的上下界end, start start = i - (le-1 )/ 2; end = i + le / 2; } } return s.substring(start, end + 1); } public int theLengthOfSubStr(String s, int left, int right, int len) { int L = left, R = right; while (L >= 0 && R < len && s.charAt(L) == s.charAt(R)) { R++; L--; } return R - L - 1; }
方法四:马拉车方法Manacher, 此方法有点和KMP类似, 都是利用已知的一些信息来避免重复计算问题。此种方法时间复杂度为O(n),非常快。
该方法存在两个小技巧,第一个利用添加特殊字符的方法解决回文子串长度的奇偶数问题。第二点利用特殊字符防止下表越界。
假设原本输入数据为babad
第一个技巧是再每一元素的左右两边都加入‘#‘字符。假设原来数据长度为n, 则‘#’字符出现的个数为n+1, 从而总的数据长度变为奇数2n+1。
#b#a#b#a#d
第二个技巧实在边界两端添加特殊字符,防止小标越界(代码中会讨论)
?#b#a#b#a#d\0
一些定义及概念
cArray表示字符串数组, p[i]表示以元素cArray[i]为对称中心的最大回文串的半径。 id表示当前最大长度的回文串对应的对称中心,mx当前最大回文串对应的右边界(不包含边界),其左边界为ml。j=2*id-i 表示i以id为对称中心的情况下对应的对称点。我们直到当i<mx时,i对应对称点j的p[j]我们是知道的,从而我们可以利用已知的p[j]来求相应的p[i],j的左边界为jl, 右边界为jr,这样会避免重复计算。
我们依据条件不同分为三种情况来计算相应的p[j]
第一种情况, i<mx, j 的回文串有一部分在 id 的之外。 此种情况下i不能继续扩展, p[i]=p[j=2*id-i]
第二种情况 i<mx, j的回文串有一部分在 id 的之内。 此种情况下i不能继续扩展, p[i]=p[j=2*id-i]
第三种情况i<mx, j 回文串左端正好与 id 的回文串左端重合,此时p[i] = p[j]
或p[i] = mx - i
,并且p[i]
还可以继续增加。
具体实现代码如下
/** * 利用马拉车方法Menacher * * @param s 目标字符串 * @return */ public String longestPalindrome4(String s) { int len = s.length(); if(len<=1) return s; len = 2*len + 3; char cArray[] = new char[len]; cArray[0] = ‘$‘; cArray[1] = ‘#‘; cArray[len-1] = ‘\n‘; for (int i = 2, j=0; i <len-1; i=i+2) { cArray[i]=s.charAt(j++); cArray[i + 1] = ‘#‘; } // p[i]保存以点cArray[i]为对称中心的最大半径长度 int[] p = new int[len]; // 表示当前最大半径的中心所在位置, mx当前最大半径所在的有边界, maxLen最大的回文串长度 int id=0, mx = 0, maxLen = -1, medium=-1; for (int i = 1; i < len-1; i++) { if(i<mx){//可以借助对称性来求p[i] p[i] = Math.min(p[2 * id - i], mx - i); }else p[i] = 1; // 长度为1 // 不用判断越界,因为左右两边分别添加‘$‘和‘\0‘防止溢出 while (cArray[i - p[i]] == cArray[i + p[i]]) { p[i]++; } // 更新当前最远右边界 if (i + p[i] > mx) { mx = i + p[i]; id = i; } System.out.println("i = " + i + " p[i] = " + p[i] + ""); // p[i] - 1 当前以i为中心的回文串的长度(去掉#) if (p[i] - 1 > maxLen) { maxLen = p[i] - 1; medium = i - maxLen; } } // 表示当前最优回文串的起始位置 medium = medium / 2; return s.substring(medium, medium + maxLen); }
标签:两种 bsp print turn 部分 特殊 不能 计算 ram
原文地址:https://www.cnblogs.com/09120912zhang/p/12585071.html