Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.(给定一个字符串 s
,找到 s
中最长的回文子串。你可以假设 s
的最大长度为 1000。)
输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。
输入: “cbbd”
输出: “bb”
回文串的特点是有一个对称区域,这里需要注意的是“区域”而不是中心点;比如abbbbbba显然“bbbbbb”就是这个对称区域;我是想遍历字符串,然后以当前字符为起点,首先找到对称中心,然后分别向右、向左扩展,记录当前回文串的起点和终点;这样当遍历完字符串之后,就相当于穷举(《算法之道》传送门)了所有可能的结果;本质上是一种穷举的思想;当然,效率很低,前55%的提交;
//46ms
public String longestPalindrome(String s){
if(s==null||s.equals("")){
return "";
}
char[] chars=s.toCharArray();
int charsLength=chars.length;
int checkLeft,checkRight,checkTimes,start=0,end=0,checkNum;
for(int i=0;i<charsLength;i++){
checkLeft=i-1;
while(checkLeft>=0&&chars[checkLeft]==chars[i]){
checkLeft--;
}//获得对称中心的左边界
checkRight=i+1;
while(checkRight<charsLength&&chars[checkRight]==chars[i]){
checkRight++;
}//获取对称中心的右边界
//检查更新start和end
//i=checkRight-1;//增加一行代码以提高质量:
if(end-start<checkRight-checkLeft-2){
start=checkLeft+1;
end=checkRight-1;
}
checkTimes=Math.min(checkLeft,charsLength-checkRight-1)+1;
checkNum=1;
while(checkNum<=checkTimes){
if(chars[checkLeft]==chars[checkRight]){
if(checkRight-checkLeft>end-start){
start=checkLeft;
end=checkRight;
}
}else{
break;
}
checkLeft--;
checkRight++;
checkNum++;
}
}
return s.substring(start,end+1);
//23ms
public String longestPalindrome (String s) {
if (s == null || s.length() == 0) return "";
int start = 0, end = 0;
for (int i = 0; i < s.length(); i++) {
int len1 = expandAroundCenter(s, i, i); // 检测长度为奇数的子串
int len2 = expandAroundCenter(s, i, i + 1); // 检测长度为偶数的子串
int len = Math.max(len1, len2);
if (len > end - start) {
start = i - (len - 1) / 2;
end = i + len / 2;
}
}
return s.substring(start, end + 1);
}
// 返回一个回文字串的长度
private int expandAroundCenter (String s, int left, int right) {
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
left--;
right++;
}
return right - left - 1;
}
23ms的解法,其将回文串长度分为两种情况:偶数以及奇数;而由于事先无法确认是哪一种,所以计算了两种情况;之后通过start和end以及得到的len确定最长的子串;其巧妙之处在于借助起点i和len推算出start和end;
这里,我想到了在已排序的数组A中计算中位数的一个方法:记i为排序数组的最后一个元素的下标,则中位数的值等于(A[i/2]+A[(i+1)/2])/2;通过分类讨论,很明显可以得到这个结论;将length=i+1带入即可得到关于length的计算公式;
本题23ms的解法也是这样:假设回文串长度为奇数,start与i之间的距离为(2n+1)/2;假设回文串长度为偶数,start与i的距离为(2n)/2-1;而在整数除法里,(2n)/2-1与(2n-1)/2是相等的;(2n+1)/2与(2n)/2的结果是相等的;于是start与i之间的距离得以统一;对于end来说,分析是一致的;
这样看来,23ms的解法更加简洁,计算更加统一,具有形式美;那么我的解法是否可以改进呢?答案是肯定的;因为我是基于先找到中心区域,然后两边扩展;这样在for循环遍历到中心区域的左边界a时,我已经找到了包含a的最大中心(通过向右扩展);这样,当我计算完包含该中心区域的回文串长度后,我应该跳出该中心区域,也就是将i设置为右边界下标,这样算上之后的++计算,刚好开始下一个中心;否则,i仅仅加1,之后的计算便是重复的,因为中心区域并没有变;举个栗子:
[a,a,a,b,b,b,b,b,a,a,a],当i=3时,中心区域为[b,b,b,b,b],计算出回文串长度为11;当i=4时,
中心区域仍然为[b,b,b,b,b]...这样就多计算了4遍,当中心区域很长时,情况就更糟了;
换言之,如果加以修改,自然可以提高不少计算时间;当然,这和数据特征有关;
不知道看到这里的你有没有这样的疑问:你为什么不一次写好呢?实际上,我在LeetCode第三题 中也改进了代码;这是因为,前几个月我开始做LeetCode上的题,一共也就做了前50道;感觉很笨重,解题就靠for和while循环了,毫无章法可言,倒也不是为了赶时间而潦草应付,只是想看看如果不学习充电,自己的水平如何,实际上很菜(基本上没有优化代码的意识,不过现在也不迟,哈哈哈);折腾了几个月后,现在终于有时间了,为了在接下来的做题中更加专业,我打算回顾以前做的这50道题,边回顾、复习边学习总结优秀的解法技巧,结果一分析就发现当时的自己是真的菜,当然,现在也是:-)~
原来运行结果:
改善后结果:
(测试用例增加了,但是执行时间减少啦~而这只是一行代码:i=checkRight-1)