思路:
Manacher算法在O(n)时间复杂度求所有回文子串。
思路为:当求后面某一位为中心时的回文子串时,对前面的回文字串信息的复用。类似KMP。本质还是动态规划。
核心思想是:当前点i为中心的对称长度 基于上一层中心点mid与i的对称点p。
维护中心点mid和最远的r,当[mid,i]中的点和[p,mid]中的点对称,利用对称性,基于以p为中心的部分点,能预先求出以i为中心的部分点,即预先求出以i为中右边的延申点为min(r-i,dp[p])
考虑到回文子串长度还有奇数或偶数,需要对原字符串S进行预处理,插值为另一个字符串T(插值方式见网上资料)。当T中以原字符串的字符为中心时对应奇数子串,以插入的字符“#”为中心时对应偶数子串。之后在将T中包含的回文信息,转换回S的回文信息(从起始字符的对应关系和长度的对应关系,转换到原字符串的回文子串)。
参考:有什么浅显易懂的Manacher Algorithm讲解? - 知乎
题1:leetcode5. Longest Palindromic Substring
模板题:
class Solution {
public String longestPalindrome(String s) {
int n=s.length();
int m=2*n+3;
char[] chs=new char[m];
for(int i=0,j=2;i<n;i++,j+=2){
chs[j]=s.charAt(i);
}
for(int j=1;j<m;j+=2){
chs[j]='#';
}
chs[0]='^';
chs[m-1]='$';
//dp记录了插值后的回文子串半边的长度,等于原字符串回文子串的总长度
int[] dp=new int[m];
//每次维护一个之前最远的回文子串,求下一个子串时可以利用之前子串的对称性,最大程度复用之前的信息
int mid=0,r=0;
for(int i=1;i<m-1;i++){
int p=2*mid-i;
if(r>i){
dp[i]=Math.min(dp[p],r-i);
}else{
dp[i]=0;
}
while(chs[i-dp[i]-1]==chs[i+dp[i]+1]){
dp[i]++;
}
if(i+dp[i]>r){
r=i+dp[i];
mid=i;
}
}
int idx=0,len=0;
for(int i=0;i<m;i++){
if(dp[i]>len){
len=dp[i];
idx=i;
}
}
//从起始字符的对应关系和长度的对应关系,转换到原字符串的回文子串
int start=(idx-len)/2;
int end=start+len;
return s.substring(start,end);
}
}
题2:leetcode1960. Maximum Product of the Length of Two Palindromic Substrings
因为这里只需要求奇数长度回文子串,所以不用插值。之后用两个数组记录左右两边的最大回文子串,用双指针求。
class Solution {
public long maxProduct(String s) {
int n=s.length();
char[] chs=s.toCharArray();
int[] dp=new int[n];
int mid=0,r=0;
for(int i=1;i<n-1;i++){
int p=2*mid-i;
if(r>i){
dp[i]=Math.min(dp[p],r-i);
}else{
dp[i]=0;
}
while(i-dp[i]-1>=0&&i+dp[i]+1<n&&chs[i-dp[i]-1]==chs[i+dp[i]+1]){
dp[i]++;
}
if(i+dp[i]>r){
r=i+dp[i];
mid=i;
}
}
long[] L=new long[n];
long[] R=new long[n];
for(int i=0,j=0;j<n;j++){
if(j>0){
L[j]=L[j-1];
}
while(i+dp[i]<j){
i++;
}
L[j]=Math.max(L[j],(j-i)*2+1);
}
for(int i=n-1,j=n-1;j>=0;j--){
if(j<n-1){
R[j]=R[j+1];
}
while(i-dp[i]>j){
i--;
}
R[j]=Math.max(R[j],(i-j)*2+1);
}
long ret=0;
for(int i=0;i<n-1;i++){
ret=Math.max(ret,L[i]*R[i+1]);
}
return ret;
}
}