求最长回文子串,大概有以下几种方法:
1、(错误!)先翻转再求最长公共子串 例如:abcdafdcba -> abcd (X) 即原串中包含翻转串的子串时
2、暴力(遍历每个字串,然后判断是否是回文子串并记录最长信息),时间复杂度O(n^3)
3、动态规划 时间复杂度O(n^2) 空间复杂度O(n^2)
dp[i][j] 表示下标i到j是否为回文子串,初始状态和状态转换方程如下:
dp[i][i] = true, dp[i][i+1] = (s[i] == s[i+1])
dp[i][j] = (dp[i+1][j-1] && s[i] == s[j])
4、遍历每个中心点和每两个中心点(相邻相等时),向左右两个方向扩展 时间复杂度O(n^2) 空间复杂度O(1)
5、manacher算法 时间复杂度O(n)
主要包含以下几个步骤:(以waabwswfd为例)
原串: w a a b w s w f d
新串: ^ # w # a # a # b # w # s # w # f # d #
辅助数组P: 1 2 1 2 3 2 1 2 1 2 1 4 1 2 1 2 1 2 1
在原串中间插入#,巧妙的将回文子串变成以中间元素为对称的子串。
辅助数组P[i] 代表的是以i为中心的回文的半径,不难看出在原串中能达到的最长回文子串长度为P[i] - 1
计算P[i]时,用mx记录当前最长回文子串能到达的最右位置,id记录当前能达到最长回文子串的中心(mx = id + p[id])
有公式:p[i] >= min(p[j], mx - i)
int j = 2 * id - i;// j为以id为中心i的对称点
if(mx > i)
p[i] = min(p[j], mx - i);
else
p[i] = 1;
while(deString[i - p[i]] == deString[i + p[i]])
p[i]++;
即以id为中心的回文子串和以j为中心的子串重叠的部分,又对称性可知p[i]至少是那么长,后面手动扩展即可。
最后遍历p即可找到最长回文子串。
代码如下:([leetcode] Longest Palindromic Substring)
class Solution {
public:
string longestPalindrome(string s) {
if(s.length() <= 1)
return s;
string deString = decorateString(s);
int *p = new int[deString.length()];
memset(p, 0, sizeof(p));
int mx = 0, id = 0;
for(int i = 1; i < deString.length(); ++i)
{
int j = 2 * id - i;
if(mx > i)
p[i] = min(p[j], mx - i);
else
p[i] = 1;
while(deString[i - p[i]] == deString[i + p[i]])
p[i]++;
if(i + p[i] > mx)
{
mx = i + p[i];
id = i;
}
}
int maxLength = 0, center = 0;
for(int i = 0; i < deString.length() - 1; ++i)
{
if(p[i] > maxLength)
{
maxLength = p[i];
center = i;
}
}
delete[] p;
return s.substr((center + 1 - maxLength) / 2, maxLength - 1);
}
private:
string decorateString(string s)
{
string deString = "^#";
for(int i = 0; i < s.length(); ++i)
{
deString += s[i];
deString += "#";
}
return deString;
}
};
6、后缀数组 具体见july大神的文章
后缀树