题目1 : 最长回文子串
时间限制:1000ms
单点时限:1000ms
内存限制:64MB
描述
小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相帮助,在编程的学习道路上一同前进。
这一天,他们遇到了一连串的字符串,于是小Hi就向小Ho提出了那个经典的问题:“小Ho,你能不能分别在这些字符串中找到它们每一个的最长回文子串呢?”
小Ho奇怪的问道:“什么叫做最长回文子串呢?”
小Hi回答道:“一个字符串中连续的一段就是这个字符串的子串,而回文串指的是12421这种从前往后读和从后往前读一模一样的字符串,所以最长回文子串的意思就是这个字符串中最长的身为回文串的子串啦~”
小Ho道:“原来如此!那么我该怎么得到这些字符串呢?我又应该怎么告诉你我所计算出的最长回文子串呢?
小Hi笑着说道:“这个很容易啦,你只需要写一个程序,先从标准输入读取一个整数N(N<=30),代表我给你的字符串的个数,然后接下来的就是我要给你的那N个字符串(字符串长度<=10^6)啦。而你要告诉我你的答案的话,只要将你计算出的最长回文子串的长度按照我给你的顺序依次输出到标准输出就可以了!你看这就是一个例子。”
样例输入
3
abababa
aaaabaa
acacdas
样例输出
7
5
3
注意用时限制,时间复杂度应为O(n)。尝试了两种方法:
1.用暴力枚举输入字串的所有子串判断是否为回文子串并返回最长字串的长度(复杂度O(n^3))
2. 依次遍历字符串中的每个字符,判断以当前字符为中心时左右两边是否构成回文子串(需区分奇偶个数时的情形,复杂度为O(n^2))
以上两种方法均超时,不满足题意。
参照他人做法,应用Manacher(马拉车)算法。
链接处https://segmentfault.com/a/1190000003914228有详细分析。
Manacher算法实质上是在方法2的基础上提出的改进算法。由于方法2在区分奇偶时仍存在重复比较同一段字符子串的问题,故改算法在原方法上进行了三点改进:
1. 在字符串中插入“#”使得字符长度为奇数
2. 获取每个位置对应的最长的回文子串长度,并保存在数组RL中(存储回文串的半径长度)。通过记录当前访问到的所有回文子串最右端的位置MaxRight,及其对应的回文子串的中心位置pos,可对RL[i]进行更新,更新是需分为两种情况: 若i<MaxRight
, 则 RL[i] = min(RL[2*pos - i], MaxRight - i)
; 否则的话,RL[i] = 1。
(对于 i<MaxRight
的理解:若 i 关于 pos 对称的位置 j 回文长度较短,则 RL[i] 应大于等于 RL[j] ; 若j较长,则 RL[i] 应大于等于MaxRight - i,因为与 j 对应的该部分一定是回文子串。以此位置继续向左右两边遍历从而省去了重复遍历)
代码如下:
#include<iostream>
#include<string>
#include<algorithm>
#include<vector>
using namespace std;
int Manacher(const string &res)
{
int n = res.size();
string s = "#";
for(int i=0; i<n; i++)
{
s += res[i];
s += "#";
}
n = s.size();
vector<int> RL(n);
int MaxRight = 0, pos = 0, len = 0;
for(int i=0; i<n; i++)
{
if(i<MaxRight) RL[i] = min(RL[2*pos-i], MaxRight-i);
else RL[i] = 1;
while(i-RL[i]>=0 && i+RL[i]<n && s[i-RL[i]]==s[i+RL[i]]) ++RL[i];
if(RL[i]+i-1>MaxRight)
{
MaxRight = RL[i] + i - 1;
pos = i;
}
len = max(len, RL[i]-1);
}
return len;
}
int main()
{
int n; cin >> n;
string str;
while(n--)
{
cin >> str;
cout << Manacher(str) << endl;
}
return 0;
}
leetcode参考链接:https://articles.leetcode.com/longest-palindromic-substring-part-ii/
另,上述提及的方法2的实现过程 (空间复杂度为O(1) ):
class Solution{
public:
string longestPalindrome(string s)
{
int n = s.length();
if(n==0) return "";
string res = s.substr(0, 1);
for(int i=0; i<n-1; ++i)
{
string p1 = expand(s, i, i); //对称中心为s[i]
if(p1.length()>res.length()) res = p1;
string p2 = expand(s, i, i+1); // 对称中心在s[i]和s[i+1]之间
if(p2.length()>res.length()) res = p2;
}
return res;
}
string expand(string s, int left, int right)
{
int n = s.length();
while(left>=0 && right<=n-1 && s[left]==s[right])
{ //由中心位置向双边扩散
--left;
++right;
}
return s.substr(left+1, right-left-1);
}
};