标签:回文子串 算法 manacher 最长回文子串 动态规划
题意:给定一个字符串s,找出该字符串中最长的回文子串。
字符串如“abcba”,”abbbba”这样呈中心对称的子串称为回文串。该题目是一个老题了,有多种不同的解法,我整理一下方便以后查询。
这个方法是我们看到这个题目后最容易想到的方法,暴力搜索所有的子串,判断每个子串是否是回文串;我们用一个二维空间记录已计算过的子串是否为回文串,这样之后针对每个新子串进行判定的时候可以利用之前记录信息辅助判断。基本的动规方程如下:
该方法的代码很简单,可去网上查询,最终的时间复杂度是O(n^2).
对于回文子串,我们还需要想到其性质:字符串关于中心位置对称。那么可以看到若子串s[3,5]不是回文串的话,那么s[2,6],s[1,7]等必然不是回文串,利用该性质我们可以省去一些不必要的比较计算。
相对于第一种方法,肯定有着更好的实际运行效果。
参考代码如下:
string longestPalindrome(string s)
{
if (s.empty()) return "";
if (s.size() == 1) return s;
int min_start = 0, max_len = 1;
for (int i = 0; i < s.size();)
{
if (s.size() - i <= max_len / 2) break;//不会超过最大回文串长度,直接跳出
int j = i, k = i;
while (k < s.size()-1 && s[k+1] == s[k]) ++k; // 跳过重复的元素.
i = k+1;
while (k < s.size()-1 && j > 0 && s[k + 1] == s[j - 1]) { ++k; --j; } // 向左右两边扩张.
int new_len = k - j + 1;
if (new_len > max_len) { min_start = j; max_len = new_len; }
}
return s.substr(min_start, max_len);
}
思路即是 从字符串的第一个字符做中心点开始,不断向串的两边扩张直到不能扩张,之后将中心点右移,不断重复操作;过程中记录下回文串起始点和回文长度。
当然该方法的最坏的时间情况仍然是O(n^2). 对于s=”aaaaaaaa”这样的特殊字符串,每个中心点都会左右扩张到字符串的边缘,因此此时时间复杂度为O(n^2).
该算法从网上学来,充分巧妙的利用了回文串的性质,能保证线性的时间复杂度。
算法思路:
首先在每相邻两个字符间插入一个分隔符(这个分隔符要在原串中没有出现过),一般都用‘#’分隔,这样就将奇数长度与偶数长度回文串统一起来考虑了(回文串长度全为奇数了),然后用一个辅助数组P记录以每个字符为中心的最长回文串的半径。为了方便理解,可以参照下图:
从新字符串的开始字符为中心点,不断向串的两边扩张,直到不能扩张,之后将中心点右移,不断重复操作;
算法中要维护一个中心点(center)和一个边界(boundary).从新的字符串的首字符开始,不断向两边扩张,直到达到以该中心点构成的最长回文子串,此时若该回文串的右边界大于boundary,那就更新boundary和center。也就是说,boundary记录算法运行至今的最偏右的右边界…
算法过程中 同时记录最大回文子串的长度和起始点。
参考代码如下:
string longestPalindrome(string s)
{
string t;
for(int i=0;i<s.size();i++)
t=t+"#"+s[i];
t.push_back(‘#‘);
vector<int> P(t.size(),0);
int center=0,boundary=0,maxLen=0,resCenter=0;
for(int i=1;i<t.size()-1;i++)
{
int j= 2*center -i;
if(boundary>i)
{
P[i] = min(P[j],boundary-i);
}else
P[i] =0;
while(i-1-P[i]>=0 && i+1+P[i]<t.size() && t[i+1+P[i]] == t[i-1-P[i]])
P[i]++;
if(i+P[i]>boundary)
{ // update center and boundary
center = i;
boundary = i+P[i];
}
if(P[i]>maxLen)
{ // update result
maxLen = P[i];
resCenter = i;
}
}
return s.substr((resCenter - maxLen)/2, maxLen);
}
时间复杂度分析:boundary在整个算法过程中是一直向右移动的,并且每次移动都是因为新的对称字符的比较,通过聚合分析方法来考虑代码段
while(i-1-P[i]>=0 && i+1+P[i]<t.size() && t[i+1+P[i]] == t[i-1-P[i]])
P[i]++;
这里的代码执行 引起了boundary的向右扩张,而boundary最大只能是n。所以整个算法的时间复杂度是线性的,即O(n)。
本文分析了三种不同的求解最长回文子串的方法,希望能给读本文的人一点收获和思考。
版权声明:本文为博主原创文章,未经博主允许不得转载。
标签:回文子串 算法 manacher 最长回文子串 动态规划
原文地址:http://blog.csdn.net/zhaoyunfullmetal/article/details/46696917