鉴于原文并没有对什么是回文做出解释,我先说明一下回文的意思:所谓回文字符串意思是这个字符串顺着读和逆序读结果都一样,例如字符串”aba“顺着逆着读都是一样的。
寻找一个字符串中的最长回文子串是一个经典的算法问题,在这边文章中,我将归纳出3种不同的求解方法。
1.最朴素的方法
最直接的来说,我们可以通过检验每一个子串是不是回文来实现,这样做的时间复杂度是O(n^3)。这只是一个开始,我们需要更好的解决办法。以下是实现代码:
bool IsPalindromic(string& str)
{
int nLen = str.length();
for (int i=0;i<nLen;i++)
{
if (str[i]!=str[nLen-i-1])
{
return false;
}
}
return true;
}
string FindMaxPalindromic(string& str)
{
int nMaxPlinLen = 0;
string strLongestPalindromic;
int nLen = str.length();
for (int i=0; i<nLen; i++)
{
for (int j=i+1; j<nLen; j++)
{
int nNewLen = j-i;
string strTemp = str.substr(i,nNewLen);
if (IsPalindromic(strTemp)&&nNewLen>nMaxPlinLen)
{
strLongestPalindromic = strTemp;
nMaxPlinLen = nNewLen;
}
}
}
return strLongestPalindromic;
}
2.动态规划的方式
假设S代表某个字符串,i和j分别代表S字符串的下标,定义一个二维数组table[i][j]代表字符串S中从下标i到下标j中间的字符所形成的子串是否为回文字符串,当值为1时代表为是,值为0是代表否。
则有定理如下
table[i][i] == 1; table[i][i+1] == 1 => s.charAt(i) == s.charAt(i+1)同时可以得出以下推论
table[i][j] == 1 => table[i+1][j-1] == 1 && s.charAt(i) == s.charAt(j)
时间复杂度为O(n^2),空间复杂度为O(n^2),代码实现如下:
string FindLongestPalindrome2(string& str)
{
if (str.empty())
{
return NULL;
}
int nLen=str.length();
if (nLen<=1)
{
return str;
}
int nMaxLen = 0;
string strLongestStr;
/*int table[nLen][nLen];*/
int** table = (int**)malloc(sizeof(int*)*nLen);
for (int i=0; i<nLen; i++)
{
table[i] = (int*)malloc(sizeof(int)*nLen);
memset(table[i],0,nLen);
}
/*int** table = new int[nLen];*/
//每个独立的字符也算回文,将其情况填入二维数组中
for (int i=0; i<nLen; i++)
{
table[i][i] = 1;
}
//紧邻的两个相同的字符合在一起也算回文,将其情况更新到二维数组中
for (int i = 0; i<nLen - 2; i++)
{
if (str[i]==str[i+1])
{
table[i][i+1] = 1;
}
}
//在1和2的前提条件下按照推理定理更新整个table
for (int k = 3; k<=nLen; k++)//子串长度从3开始
{
for (int i = 0; i<nLen-k; i++)
{
int j = i+k-1;
if (str[i] == str[j])
{
table[i][j]=table[i+1][j-1];//此处就是运用了推理
if (table[i][j]==1&&k>nMaxLen)
{
strLongestStr = str.substr(i,k);
}
}
else
{
table[i][j] = 0;
}
}
}
for(int i=0; i<nLen; i++)
{
free(table[i]);
}
free(table);
return strLongestStr;
}
且如果将table表输出的话,我们可以很容易在表中找到最长回文字符串的位置,表格可以很形象的反映出有多少回文以及各个回文的长度,这点上是优于第一种方法的。
3.一种简单的实现方法
这种方法的时间复杂度为O(n^2)空间负责度为O(1)。至于如何简单,废话不多说,请看如下代码:
string Helper(string& str, int nBegin, int nEnd)
{
while(nBegin>=0&&nEnd<str.length()-1&&str[nBegin]==str[nEnd])
{
nBegin--;
nEnd++;
}
return str.substr(nBegin+1,nEnd-nBegin-1);
}
string FindLongestPalindrome(string& str)
{
if(str.empty())
{
return NULL;
}
int nLen = str.length();
if(nLen <= 1)
{
return str;
}
string strLongest = str.substr(0,1);
for(int i=0; i< nLen; i++)
{
string strTemp = Helper(str, i, i);
if (strTemp.length()>strLongest.length())
{
strLongest = strTemp;
}
//因为回文可能是偶数个字母,需要以两个挨着的字母为中心进行扩展
strTemp = Helper(str,i,i+1);
if (strTemp.length()>strLongest.length())
{
strLongest = strTemp;
}
}
return strLongest;
}
此种方法基本意思就是以单个字符或者两个字符为中心进行扩展找到最长的回文字符串。
4.Manacher的算法
该算法的时间负责度是O(n),空间复杂度是O(1),但由于极其复杂且不是典型的做法,因此不在花时间去介绍了。
总结:以上主要归纳了3种求回文的方法,现将其再次概括如下。
(1)完全遍历每个子串,记录其长度,并检查其是否为回文,易理解掌握,但时间复杂度为O(n^3),空间复杂度为O(1)。
(2)利用动态规划的原理首先求得单个字符是否为回文(必然是),其次求得双个字符是否为回文,然后利用推论依次找到各个长度的字符串是否为回文,用一张表可以清晰的反映出回文存在的情况,这是方法1和方法3所不具备的,但空间复杂度增加到O(n^2),不过其时间复杂度降到了O(n^2)。
(3) 最后是利用中心点扩散的方式寻找最长回文,这种方法简单且高效,其时间复杂度为O(n^2),空间复杂度为O(1)。
但方法(1)和方法(3)都存在一个缺点就是如果最长回文子串存在多个,那么只会取得第一个最长回文子串,但方法二却能很好的反映出所有最长回文子串。
本译文的程序全部自己调试通过,已经和原文的程序大有不同,不知道是否java的语法和c++有什么不同,个人认为原文的第二个程序在下标处理上面有问题。