题目描述:
题解:
一、暴力破解(时间复杂度:O(n3),空间复杂度:O(1),超出时间限制(TLE))
BF方法的思路比较简单,逐个检查所有的子字符串,看它是否是回文子串。
二、中心扩展算法
逐字符访问,以当前字符(奇数)或当前和下一字符(偶数)为中心,向两边扩展,检查是否为回文。共有n+n-1个中心。
int expend(string s, int left, int right)
{
int L = left;
int R = right;
while (L >= 0 && R<s.length() && s[R] == s[L])
{
L--;
R++;
}
return R - L - 1;
}
string longestPalindromeCenter(string s) {
int len = s.size();
if (len == 0 || len == 1)
return s;
int start = 0;
int end = 0;
int mlen = 0;
for (int i = 0; i<len; i++)
{
int len1 = expend(s, i, i); //odd
int len2 = expend(s, i, i + 1); //even
mlen = max(max(len1, len2), mlen);
if (mlen > end - start + 1)
{
start = i - (mlen - 1) / 2;
end = i + mlen / 2;
}
}
return s.substr(start, mlen);
}
复杂度分析:循环O(n),扩展O(n),时间复杂度O(n2);只需要恒定的内存来存储几个局部变量,空间复杂度O(1)。
二、动态规划
string longestPalindromeDP(string s) {
int len = s.size();
if (len == 0 || len == 1)
return s;
int start = 0;
int sub_len = 1;
vector<bool> tmp_v(len, false);
vector<vector<bool>> dp(len, tmp_v);
for (int i = 0; i < len; i++) //1&2
{
dp[i][i] = true;
if (i < len - 1 && s[i] == s[i + 1])
{
dp[i][i + 1] = true;
sub_len = 2;
start = i;
}
}
for (int l = 3; l <= len; l++) //3-len
{
for (int i = 0; i + l - 1<len; i++)
{
int j = i + l - 1;
if (s[i] == s[j] && dp[i + 1][j - 1])
{
dp[i][j] = true;
start = i;
sub_len = l;
}
}
}
return s.substr(start, sub_len);
}
复杂度分析:两层循环,时间复杂度为O(n2);创建dp数组,空间复杂度为O(n2)。
优化方法二,用一维数组代替二维数组。一维数组每次循环存储上图中的一行,下次循环会存储下一行(逐元素覆盖),这样每次只要检查s[i+1]就相当于二维数组时的检查s[i+1][j-1]。
string longestPalindromeDP(string s) {
int len = s.size();
if (len == 0 || len == 1)
return s;
int start = 0;
int sub_len = 1;
vector<bool> dp(len, false);
for (int j = 0; j < len; j++)
{
for (int i = 0; i <= j; i++)
{
dp[i] = (s[i] == s[j] && (j - i < 2 || dp[i + 1]));
if (dp[i] && (j - i + 1) > sub_len)
{
start = i;
sub_len = j - i + 1;
}
}
}
return s.substr(start, sub_len);
}
复杂度分析:两层循环,时间复杂度为O(n2);创建一维dp数组,空间复杂度为O(n)。
三、Manacher算法
首先,预处理,字符间插入“#”,这样做使长度总为奇数,使奇偶的情况不需要分开讨论。首尾同样添加“#”(还可以再继续添加两个表示首尾的其它符号)。
结合下图(讨论区解释)与代码即可看懂“马拉车”:
i<maxRight时:
这里得到结论:p[i] = min(maxRight - i, p[mirror])。
max(i, i+p[i])>maxRight 时,当前回文子串检查完毕,更新center与maxRight。
string preProcess(string &s){
if (s.empty())return "^$";
string s_tmp = "^";
int len = int(s.size());
for (int i = 0; i < len; i++){
s_tmp = s_tmp + "#" + s[i];
}
s_tmp += "#$";
return s_tmp;
}
string longestPalindromeManacher(string s) {
string s_new = preProcess(s);
int len = int(s_new.size());
vector<int> p(len, 0);
int c = 0, right = 0;
//Manacher
for (int i = 1; i < len - 1; i++) {
int i_mirror = 2 * c - i;
if (right > i) p[i] = min(p[i_mirror], right - i);
while (s_new[i - p[i] - 1] == s_new[i + p[i] + 1]) {
p[i]++;
}
if (i + p[i] > right) {
c = i;
right = c + p[i];
}
}
int len_max = 0, center_max = 0;
for (int i = 0; i < len; i++) {
if (p[i] > len_max) {
len_max = p[i];
center_max = i;
}
}
int start = (center_max - len_max) / 2;
return s.substr(start, len_max);
}
科学家Manacher的工作:充分利用新字符串的回文性质,计算辅助数组 p,在填写新的辅助数组 p 的值的时候,能够参考已经填写过的辅助数组 p 的值,使得新字符串每个字符只访问了一次,整体时间复杂度为O(n);创建了一维p数组,空间复杂度为O(n)。实际上此算法里可以看到中心扩展算法的影子,对比于中心扩展算法,Manacher算法用空间换时间,将此问题的时间复杂度降为线性,达到了理论上的最优。