题目:最长回文子串java
描述:给定一个字符串 s,找到 s 中最长的回文子串。你能够假设 s 的最大长度为 1000。算法
示例:编程
回文字符串可能咱们不少人在学习编程语言基础时都练习过,可是只是判断了一个字符串是不是回文的而已,而今天的题目要复杂得多了。若是咱们要找出全部的子串,再一一判断它是否是回文的,可能须要O(n<sup>3</sup>)的时间复杂度,因此咱们要对它进行优化。下面咱们要介绍的两种方法,能够将此问题的时间复杂度分别下降到O(n<sup>2</sup>)和O(n)。数组
回文字符串的特性就是中心对称,那么咱们能够假定每一个字符都是一个回文子串的中心字符,而后以此字符为中心向两侧扩展,直到再也不是回文子串为止。这样咱们就找到了以每一个字符为中心的最长回文子串,也就等于找到了最长的回文子串。不过这里有一点须要注意,那就是一个回文串的中心多是一个字符,例如"aba"就是以字符 'b' 为中心,也多是在两个字符之间,例如"abba"的中心就在两个 'b' 字符之间,只要处理好这两种状况,代码就很简单了,参考以下:app
public String longestPalindrome(String s) { int len = s.length(); if(len==0)return ""; int i = 0; int[] ends = new int[2]; while(i<len){ // 处理 "aba" 的状况 getEnds(s, len, i-1, i+1, ends); // 处理 "abba" 的状况 getEnds(s, len, i, i+1, ends); i++; } return s.substring(ends[0], ends[1]); } private void getEnds(String s, int len, int left, int right, int[] ends){ while(left >= 0 && right < len && s.charAt(left)==s.charAt(right)){ left--; right++; } if((ends[1]-ends[0])<(right-left-1)){ ends[0] = left+1; ends[1] = right; } }
这个方法也称为“马拉车”算法,能够说是解决回文字符串最好的算法了,由于它的时间复杂度仅为O(n)。下面咱们一步步分析这个算法是如何作到如此高效的。编程语言
首先,咱们用 '#' 或某个字符串中不存在的字符将字符串的每一个字符分隔开,并在首尾也插入一个 '#',以下所示:函数
以上操做解决了由于长度的奇偶性带来的问题,新的字符串长度是 2*n+1,是始终为奇数的。学习
接下来和中心扩展同样,咱们也要肯定以一个字符为中心的最长回文子串的长度。Manacher算法中有一个概念叫做“回文半径”,它指的是从中心字符到最右侧(或最左侧)字符的距离,例如"#a#"的半径就是2,。用此距离构造一个半径回文数组radius,咱们根据数组中的最大值就能够肯定最长回文子串的长度了。例如,以上示例中间的字符 'b' 的回文半径为4,构成的回文数组以下:优化
能够发现,以原串第 i 个字符为中心的最长回文子串,就是radius数组第 i 位的值减 1 。那么,咱们如何构造这个数组呢?ui
假设咱们已经求得了位置 i 处(相对于Manacher构造的字符串)的回文半径,也就是radius[i]的值,并记它的最右侧位置为 mx,以下所示:
而后咱们要计算位置 j 处的回文半径,其中 j>i。因为radius[i]的值可能为0,也可能较大,因此 j 可能小于 mx,也可能大于 mx,咱们先来看 j<=mx 时的状况。以下所示,j<mx 时,j 关于 i 的对称点 i-(j-i)(咱们记为 k),也必定大于 mx关于 i 的对称点 i-(mx-i)(咱们记为mn),以下所示:
如今,让咱们把目光聚焦到 j 的对称点 k 上,它将为咱们计算 j 的回文半径提供线索。由于radius[k]的值可能很小,以 k 为中心的回文子串彻底被以 i 为中心的子串覆盖,也可能较大超出了 i 的范围,以下所示:
对于第一种状况,能够确定 j 也将被 i 彻底覆盖,因此它的回文半径和 k 是相等的。第二种状况则代表从 mx 以后的部分是未知的,须要进一步计算。
如今咱们再来考虑 j>=mx 的状况,以下图所示,这时 j 的回文半径一点都没计算过,因此只能进一步进行计算。
根据以上思路,能够参考的代码以下:
public String longestPalindromeOptimize(String s) { if (s == null || s.length() == 0) { return ""; } char[] charArr = manacherStr(s); // 构造回文半径数组 int[] radius = new int[charArr.length]; // 当前已经计算过的最右侧下标 int mx = -1; // i 表示 mx 最大时,对应的回文子串的中心 int i = -1; // 最长回文子串的长度 int max = 0; // 最长回文子串的起点下标 int maxIndex = -1; for (int j = 0; j < radius.length; j++) { // 2*i-j 是 j 相对于 i 的对称点,也就是文章中的 k。 // 当 j<mx 时,因为以 k 为中心的子串可能被 i 彻底覆盖,也可能超出 i 的范围 // 因此,计算 咱们只能计算出 radius[j] 在 mx-j这个范围内的值 // 而当 j>mx 时,就须要彻底从头开始计算了 radius[j] = j < mx ? Math.min(radius[2 * i - j], mx - j + 1) : 1; // 计算 j<mx 时超出 mx-j 范围内的部分,或者 j 本就大于 mx时 while (j + radius[j] < charArr.length && j - radius[j] >= 0 && charArr[j - radius[j]] == charArr[j + radius[j]]) { radius[j]++; } // 更新 mx 的值和 i 的值 if (j+radius[j]>mx) { mx = j+radius[j]-1; i = j; } // 更新max的值 if (max<radius[j]) { max = radius[j]; // j 的起点坐标,相对于Manacher字符数组而言是 j-radius[j]+1 // 而相对于原数组而言,这个值是由原数组的位置乘以2再加一获得的,因此直接除以2便可 maxIndex = (j-radius[j]+1)/2; } } return s.substring(maxIndex,maxIndex+max-1); } private char[] manacherStr(String s){ StringBuilder sb = new StringBuilder(); for (int i = 0; i < s.length(); i++) { sb.append('#'); sb.append(s.charAt(i)); } sb.append('#'); return sb.toString().toCharArray(); }
Manacher算法若是咱们没有听过仍是很难想到的,不过不要气馁,咱们只是来学习的,想不到能学到也算赚到,可以理解它也算一笔不小的收获。下面这个题目较为简单,让咱们放松一下吧。
题目:Z 字形变换
描述:将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。
好比输入字符串为 "LEETCODEISHIRING" 行数为 3 时,排列以下:
L C I R E T O E S I I G E D H N
以后,你的输出须要从左往右逐行读取,产生出一个新的字符串,好比:"LCIRETOESIIGEDHN"。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
示例 1:
示例 2:
L D R E O E I I E C I H N T S G
相关源码请加QQ获取。
【感谢您能看完,若是可以帮到您,麻烦点个赞~】
更多经验技术欢迎前来共同窗习交流: 一点课堂-为梦想而奋斗的在线学习平台 http://www.yidiankt.com/
![关注公众号,回复“1”免费领取-【java核心知识点】]
QQ讨论群:616683098
QQ:3184402434
想要深刻学习的同窗们能够加我QQ一块儿学习讨论~还有全套资源分享,经验探讨,等你哦!