最长回文子串(Longest Palindromic Substring)
问题描述:给一个字符串s,返回它的最长回文子串。
问题解释:回文字符串有点类似于我们曾学过的回文联,比如“雾锁山头山锁雾,天连碧水碧连天”。比如s='abac', 输出 应该是'aba'。再比如说,ABCBA, 这个回文字符串的中心为C,长度为5。如果对此仍不理解,出门左转百度或者google,可以找到比较权威的定义。
解决方案:
一、蛮力法(brute force) time complexity:O(n^3)
这个就不上代码了,直接穷举所有情况,找到最长回文子串。
二、动态规划(dynamic programming) time complexity:O(n^2)
动态规划的思想在于减少子问题的计算量,重点是分解成一个个子问题。
我们定义P[i] [j]为Boolean 数组,当P[i] [j]为true的时候表示,s.substring(i,j+1)是回文串(s.substring(start,end)是截取s.charAt(start)到s.charAt(end-1)的子串);相反则表示不是回文串。
并且P[i] [j]可以由P[i+1] [j-1]推导出,有:P[i] [j]= (P[i+1] [j-1] && s.charAt(i)==s.charAt(j))。
分析这个表达式,比如对于s字符串ABBAC,P[1] [2]=true,P[0] [3]=(P[0+1] [3-1] && s.charAt(0)==s.charAt(3))
显然P[0] [3]也为true.
另外,动态规划的初始化为,P[i][i]=true 因为单个的字符满足回文要求;
P[i][i+1]=(s.charAt(i)==s.charAt(i+1)) 两个相邻的字符要看是否相等
则动态规划的递归实现如下:
//Recursion
// Set P[][] as Boolean array
public Boolean P(String s,int i,int j){
s=this.s;
if (i==j) {P[i][j]=true; return P[i][j];}
if (j==(i+1)) {P[i][j]=(s.charAt(i)==s.charAt(j)); return P[i][j];}
P[i][j] = P(s,i+1,j-1) && (s.charAt(i)==s.charAt(j));
return P[i][j];
}
然而递归规划如果用递归实现,虽然说理解简单,但是仍旧有相当大的重复计算量,可以采用循环的方式实现:
//how to avoid unnecessary calculation
public void solve(){
//initialize the P[i][i] and P[i][i+1]
for(int i=0;i<s.length();i++){
P[i][i]=true;
if(i<s.length()-1) //avoid out of bound of array
if (s.charAt(i)==s.charAt(i+1))
P[i][i+1]=true;
else P[i][i+1]=false;
}
//expand
for(int len=3;len<=s.length();len++)
for(int i=0;i<s.length()&&i+len-1<s.length();i++){//avoid out of bound of array
int j=i+len-1;
if(i+1<s.length() && j-1>0 && P[i+1][j-1] && s.charAt(i)==s.charAt(j))
P[i][j]=true;
else P[i][j]=false;
}
}
动态规划的时间复杂度分析:(1)初始化的时间复杂度为O(n)
(2)后面拓展的时间复杂度为O(n^2)
所以总的时间复杂度为O(n^2)
三、中心拓展法(Expand around center) time complexity:O(n^2)
这个先上代码,是leetCode上的:
public String longestPalindrome(String s) { 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) { int L = left, R = right; while (L >= 0 && R < s.length() && s.charAt(L) == s.charAt(R)) { L--; R++; } return R - L - 1; }对于主函数的For循环就是遍历字符串s,len1对应的是最长回文子串的中心为字符,len1对应另一种情况;
对于函数expandAroundCenter就是,以for循环遍历到的字符x为中心,向两个方向扩展回文子串,直至不能得到更大的回文子串或者碰到字符串的边界。返回以x为中心的最长回文子串长度。
当遍历结束后必能得到解。
时间复杂度分析:对于for循环的每一个遍历的元素,调用了expandAroundCenter函数,而对于该函数拓展的最大长度不可能大于n,所以时间复杂度小于等于O(n^2)
四、Manacher's Algorithm
这个算法又叫马拉车,估计是音译的,不过确实形象,留在下一篇再讲。
ps:
给大家一个这个问题的leetCode链接:https://leetcode.com/problems/longest-palindromic-substring/solution/