最长回文子串
--------题目描述
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例2:
输入: "cbbd"
输出: "bb"
--------解题思路
看到这个题目,我这个菜鸡第一反应是暴力求解。遍历所有子字符串,然后判断是否回文。这显然时间复杂度最少为
O
(
n
2
)
O(n^{2})
O(n2)。判断回文还需要遍历子字符串,这就造成
O
(
n
3
)
O(n^{3})
O(n3)复杂度。代码我肯定可以写出来,但是,这不是我想要的结果。我是为了学算法而刷题的,而不是为了写代码而刷!
在官方给的动态规划法提示下,我看到了减少暴力求解时间复杂度的方法。
- 动态规划法求解这个,首先考虑清楚,回文字符串长度有奇偶之分,最长奇数回文字符串都是从长度为1的回文子串开始两边慢慢往外扩展得到的;最长偶数回文字符串也是从长度为2的回文子串开始两边慢慢扩展得到的。
1.如果没有长度为2的回文子串,就不会存在长度为4的回文子串;
2.如果没有长度为3的回文子串,就不会存在长度为5的回文子串;
这个道理,想必一般人都知道,没想到的你可以看看回文串的定义,然后在草稿纸上划拉划拉。
理清楚上面的,那么接下来就是算法步骤:
初始化长度为1和2的回文串,然后在长度为1的基础上,找出所有长度为3的回文子串,在长度为2的基础上,找出所有长度为4的回文子串, . . . . . . ...... ......,依次直到最后找到长度为原字符串长度的回文串。
下面贴上我那拙劣的java代码:
class Solution {
public String longestPalindrome(String s){
/*动态规划法,遍历所有情况*/
if(s.length()==0){return s;}
int indexRight=0,indexLeft=0; //定义最大回文串左边界和右边界
int len = 1; //首先从子字符串长度为1的开始
/*长度为奇数的回文子串的左边界和右边界*/
HashMap<Integer,Integer> oddIndex = new HashMap<Integer,Integer>();
/*长度为偶数的回文子串的左边界和右边界*/
HashMap<Integer,Integer> evenIndex = new HashMap<Integer,Integer>();
//复制品
HashMap<Integer,Integer> shadow = new HashMap<Integer,Integer>();
/*初始化回文子串为1和2的*/
for(int i=0;i<s.length();++i){
oddIndex.put(i,i);
if(i<s.length()-1&&s.charAt(i)==s.charAt(i+1)){
evenIndex.put(i,i+1);
indexLeft = i;
indexRight = i+1;
}
}
while(len<s.length()){
Iterator<HashMap.Entry<Integer,Integer>> it ;
if(len%2!=0){
it = oddIndex.entrySet().iterator();
}else{
it = evenIndex.entrySet().iterator();
}
while(it.hasNext()){
int i=1;
HashMap.Entry<Integer,Integer> entry = it.next();
while(entry.getKey()-i>=0&&entry.getValue()+i<s.length()&&s.charAt(entry.getKey()-i)==s.charAt(entry.getValue()+i)){
++i;
}
if(i!=1){
shadow.put(entry.getKey()-i+1,entry.getValue()+i-1);
if((entry.getValue()+2*i-2-entry.getKey())>(indexRight-indexLeft)){
indexLeft = entry.getKey()-i+1;
indexRight = entry.getValue()+i-1;
}
}
}
if(len%2!=0){
oddIndex = shadow;
}else{
evenIndex = shadow;
}
shadow.clear();
++len;
}
return s.substring(indexLeft,indexRight+1);
}
}
以上代码就是按照上面思想算的,这样比暴力解法好的地方就是,有条件的穷举。例如在穷举回文串时,当字符串中不存在长度为3的回文子串时,那后面奇数长度的就不需要遍历了,省去了遍历时间。
下面是提交结果:
- 马拉车算法
该算法思想也比较简单,就是利用回文的对称特性,减少不必要的迭代。
这个算法是专门用来解决找回文串的,我想大家应该也知道算法思想吧,我这里就不废话了。下面是我拙劣的代码:
public String reconStr(String s){
/*函数功能是向输入字符串插入#和$
eg: input:"abedce" --> output:"$#a#b#e#d#c#e#"
* */
StringBuilder str = new StringBuilder(s);
for(int i=s.length();i>=0;i--){str.insert(i,'#');}
str.insert(0,'$');
return str.toString();
}
public String longestPalindrome(String s) {
s = reconStr(s);
/*接下来就是计算p[i]数组*/
int MaxRight=0,pos=0,max=0;
int[] RL = new int[s.length()];
for(int i=1;i<s.length();++i){
if(i<MaxRight){RL[i] = RL[2*pos-i]<(MaxRight-i)?RL[2*pos-i]:(MaxRight-i);}
else{RL[i] = 1;}
/*不在MaxRight覆盖范围,那就只能一步一步外扩*/
while(i-RL[i]>=0&&i+RL[i]<s.length()&&s.charAt(i-RL[i])==s.charAt(i+RL[i])){++RL[i];}
/*更新pos和MaxRight*/
if(i+RL[i]-1>MaxRight){
pos = i;
MaxRight = RL[i]+i-1;
}
if(RL[i]>RL[max]){max = i;}
}
StringBuilder result = new StringBuilder();
for(int i=max-RL[max]+1,j=0;i<=RL[max]+max-1;++i){
if(s.charAt(i)!='#'&&s.charAt(i)!='$'){
result.append(s.charAt(i));
++j;
}
}
return result.toString();
}
提交结果如下:
反思
马拉车算法我看了一天,才写出来。太笨了。之前学数据结构的时候没听过这个算法。算法比较简洁。