问题描述:寻找字符串的最长回文子序列。
Example 1:
Input: "babad" Output: "bab" Note: "aba" is also a valid answer.
Example 2:
Input: "cbbd" Output: "bb"
Soulution 1 (中心展开):回文序列从中心字符向两边扩展两边元素相同,需要考虑奇数个和偶数个的情况。注意字符串为空的判别: if(s==null || s==""). 注意循环的判断条件不能到length-1为止,这样的话如果字符长度是1 就会返回空字符串。应该在子函数中设置判断。 Time Complexity - O(n2), Space Complexity - O(1)
class Solution {
public String longestPalindrome(String s) {
if(s==null || s=="")
return "";
String res = "";
for(int i=0;i<s.length();i++){
String temp = findstr(s,i,i); //奇数序列
if(temp.length()>res.length())
res = temp;
temp = findstr(s,i,i+1); //偶数序列
if(temp.length()>res.length())
res = temp;
}
return res;
}
String findstr(String s, int i, int j){
while(i>=0&&j<s.length()&&s.charAt(i)==s.charAt(j)){
i--;
j++;
}
return s.substring(i+1,j);//注意substring不包含j字符
}
}
Soulution 2 :Manacher's Algorithm
马拉车算法时间复杂度O(n),这个方法的最大贡献是在于将时间复杂度提升到了线性。
方法重点:(参考Java实现以及讲解,讲解的非常形象给出实例,c++实现)
1. 为了不用区分奇偶,在字符串每个字符两边加上#,为了防止越界,在字符串头加上$,尾部加上@。(具体解释参考上面的链接)
2. 核心: 之所以时间复杂度低是因为减少了每个字符为中心的判别。需要保存和新的字符串等长的数组,每个元素为对应位置元素为中心的最长回文序列半径(半径包含中心点)。以id为中心最远的回文字符串(不一定是最长的),mx是他的右端点。我们此时的index关于id的对称点为j=2*id-i, 如果i>mx, 那就只能和方法以一样从中心展开找,p[i]=1开始. 如果i<mx,有两种情况:1)2如果i+p[j]>mx, 则至少i到mx是回文序列,p[i]至少是mx-i.
2)如果i+p[j]<mx, p[i]至少是p[j]
得到p[i]最小值之后再向两边寻找直到不满足。
3. 规律:最长子串的长度是半径减1,起始位置是中间位置减去半径再除以2。(参考第二个链接,例子很详细)
注意这里最长字串的半径和中心(reslen,resid)不同于最远字串的半径和中心(id,mx)
4. substring(i,j)不包含j, 长度为a的字符串:substring(i, i+a)
class Solution {
public String longestPalindrome(String s) {
if(s==null || s=="")
return "";
String t = "$#";
for(int i=0;i<s.length();i++){
t = t + s.charAt(i);
t = t + "#";
}
t =t+"@";
int[] p = new int[t.length()];
int id=0, j=0,mx=0;
int reslen = 0, resid =0;
for(int i=0;i<t.length();i++){
j = 2*id -i;
p[i] = i>mx? Math.min(p[j],mx-i):1; //核心部分!!!
while((i-p[i]>=0)&&(i+p[i]<t.length())&&(t.charAt(i-p[i])==t.charAt(i+p[i])))
p[i]++;
if(i+p[i]>mx){
id=i;
mx=i+p[i];
}
if(p[i]>reslen){
reslen=p[i];
resid=i;
}
}
return s.substring((resid-reslen)/2, (resid-reslen)/2+reslen-1);
}
}