T5 最长回文子串
力扣https://leetcode.cn/problems/longest-palindromic-substring/法一:中心扩展
基本思想:遍历字符串中的每个字符将其作为中心,向两边进行扩展,直至到达字符串头尾或不为回文子串,再计算长度即可,保留最长的范围。
class Solution {
public:
pair<int, int> expand(string &s, int left, int right){
while(left>=0 && right<s.length() && s[left]==s[right]){
left--;
right++;
}
return make_pair(left+1, right-1); //注意此处需要向中心聚拢一,因为当前left和right区间内不满足条件
}
string longestPalindrome(string s) {
int n=s.length();
int start=0, end=0;
for(int i=0; i<n; i++){
auto [a1,b1]=expand(s,i,i);
auto [a2,b2]=expand(s,i,i+1);
if(b1-a1>end-start){
start=a1;
end=b1;
}
if(b2-a2>end-start){
start=a2;
end=b2;
}
}
return s.substr(start,end-start+1);
}
};
个人感觉这种算法理解起来十分简单,但是感觉还是没有充分利用到前序已经求解的结果。
法二:Manacher 算法
核心思想:利用前序信息。假定回文子串长度为奇数,引入臂长的概念,中心为s[j],回文子串为s[j-length]到s[j+length],总长度为2*length+1,臂长为length。
若已知s[j]的臂长为length,且目前遍历的中心i满足i<j+length,即在以j为中心的回文子串中,则可利用前序信息:
① 首先查找i关于j的对称点2*j-i,若该点的臂长为n,则i的臂长至少为min(j+length-i, n);
解释:n的情况:以对称点为中心的回文子串完全包含在以j为中心的回文子串中;
j+length-i的情况:以对称点为中心的回文子串完全未包含在以j为中心的回文子串中;
② 继续从i+min(j+length-i, n)+1向外拓展。
还需要考虑长度为偶数的回文子串,在字符串头尾和每两个字符串中间插入一个特殊字符如#(需要注意不能为字符串中原有的字符),原来的偶数长度回文串就可以转换为奇数长度回文串,如aa变为#a#a#
实际实现中,直接将原字符串添加特殊字符串在进行处理,维护一个当前到达的最右回文子串。
class Solution {
public:
int expand(string &s, int left, int right){ // 用于计算臂长
while(left>=0 && right<s.length() && s[left]==s[right]){
left--;
right++;
}
return (right-left-2)/2;
//与前一种算法一样,注意此处需要向中心聚拢一,因为当前left和right区间内不满足条件
}
string longestPalindrome(string s) {
int start=0,end=0;
string t="#";
for(int i=0;i<s.length();i++){
t+=s[i];
t+='#';
}
s=t;
vector<int> arm;
int right=-1,j=-1; //j为最长回文子串的中心
int n=s.length();
for(int i=0;i<n;i++){
int cur;
if(right>=i){
int sym=2*j-i; //对称点
int min_arm=min(arm[sym],right-i);
cur=expand(s,i-min_arm,i+min_arm);
}
else{
cur=expand(s,i,i);
}
arm.push_back(cur);
//下面对最长回文子串和最右回文子串进行更新
if(i+cur>right){
j=i;
right=i+cur;
}
if(cur*2+1>end-start+1){
start=i-cur;
end=i+cur;
}
}
string ans;
for(int i=start;i<=end;i++){
if(s[i]!='#'){
ans+=s[i];
}
}
return ans;
}
};