回文字符串,是指正读和倒读的结果一样的字符串,从结构上来看,两侧的字符呈中心对称。在汉语中,有很多有趣的回文诗词,回文对联熟语,比如“响水池中池水响,黄金谷里谷金黄”、“雾锁山头山锁雾,天连水尾水连天”等。根据其结构特征,我们很容易设计出一个判断字符串是否回文的算法:
isPalindromic(s)
boolean flag=true
char[] chars=s.toCharArray()
for i=0 to (chars.length-1)/2
if chars[i]!=chars[chars.length-1-i]
flag=false
break
return flag
现在我们看一道leetcode中关于取一个字符串中最长的回文字的问题:
Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.
首先最简单的方法,暴力穷举所有的子串,然后分别利用上述的方法进行判断即可。遍历+判断,穷举法总的时间复杂度为O(n^3).
bruteForceForPalindromic(S)
String result
for i=0 to s.length()-1
for j=i+1 to s.length()-1
substring=s.substring(i,j)
if(isPalindromic(substring) and substring.length()<result.length())
result=substring
很显然,上述暴力算法在运行效率上是极低的。主要原因是其存在着大量重复的判断,比如当substring(i,j)不是回文串时,我们可以确定substring(i-1,j+1)一定不是回文串,不用进行判断即可。由此我们考虑构造一个从中心节点双向探测的方法来获取最长的回文子串,即判断每一个中心节点可以构成的回文的最长串。现在的主要问题是,一共有多少个这样的中心节点呢?答案是2*n-1个,这是考虑了奇数回文和偶数回文的两种不同情况,如下图所示。
/**
* 以每一个点为中心点,向两边探测的方法判断回文
* @param s
* @return
*/
private static String getLongestPalindromic1(String s) {
String result="";
String sub="";
char[] chars=s.toCharArray();
int n=s.length();
int j=0;
//以此以每一个字符作为回文的中心节点centercode
for(int i=0;i<n;i++) {
//奇数回文探测
j=0;
while(j<=i &&i+j<n&& chars[i-j]==chars[i+j]) {
sub=s.substring(i-j, i+j+1);
j++;
}
result=result.length()<sub.length()?sub:result;
//偶数回文探测
j=0;
while(j<=i && i+j+1<n && chars[i-j]==chars[i+j+1]) {
sub=s.substring(i-j, i+j+2);
j++;
}
result=result.length()<sub.length()?sub:result;
}
return result;
}
现在我们接着回文串的子结构性质进行探讨,并考虑用动态规划的方法进行解决。
首先我们使用一个布尔类型的二维数组dptable[n][n]来表征第i个字符到底j个字符构成的子串是否回文,构成则值为true,否则为false。考虑到对于回文子串substring(i+1,j-1)当且仅当s[i]==[j]时,可以得到substring(i,j)是回文的。现在可以根据这样的性质去构造递推关系:
最后我们考虑动态规划的基础值的情况:
对于当个字符来说,其本身认为是回文的,则可以得到dptable[i][i]=true;
对于相邻的两个字符来说,如果两个字符是相等的,则认为其是回文的,否则不构成回文,所以dptable[i][i+1]=s[i]==s[i+1]。
综合上述的讨论,算法的时间复杂度为O(n^2)。Java实现如下:
/**
* 动态规划的方法求解最长回文子串
* @param s
* @return
*/
private static String getLongestPalindromicDP(String s) {
String result="";
char[] chars=s.toCharArray();
int n=chars.length;
boolean[][] dptable=new boolean[n][n];
//初始化动态规划数组
for(int i=0;i<n;i++) {
for(int j=0;j<n;j++) {
dptable[i][j]=false;
}
}
//设置规划的base值
for(int i=0;i<n;i++) {
dptable[i][i]=true;
}
for(int i=0;i<n-1;i++) {
if(chars[i]==chars[i+1]) {
dptable[i][i+1]=true;
}
}
//规划计算
for(int j=2;j<n;j++) {
for(int i=0;i<j-1;i++) {
dptable[i][j]=dptable[i+1][j-1]&&(chars[i]==chars[j]);
}
}
//取得最长的回文串
for(int i=0;i<n;i++) {
for(int j=0;j<n;j++) {
if(dptable[i][j]==true) {
result=result.length()<=j-i?s.substring(i,j+1):result;
}
}
}
return result;
}