零、预备知识
Manacher用于在一个字符串中找到最长的回文子串。
回文串:正着念和反着念一样,例如aabbaa,anna等。
注意子串与子序列的区别:
子串必须是在原字符中可以找到的。比如 " I am a student"。am是子串(当然也是子序列),但是aa就不是子串了(是子序列)。
一、算法原理
Manacher算法通常人称马拉车算法,用于在一个字符串中找到最长的回文子串。
1.首先因为奇偶个数的不同,所以判断回文的方式不一样,因此manacher算法通过在字符串两边和每个字符之间插入一个相同的字符来字符串转换为奇数个的字符串。插入的字符可以是任意字符,但是必须保证插入的字符都相同,一般常用‘#’作为插入字符。例如,原始字符串aaabbbaaa,插入后的字符串则为#a#a#a#b#b#b#a#a#a#。
2.manacher算法使用了3个辅助变量:
1) int pArr[ ] 存储以每个位置i为中心的最长回文串的最右边界right到位置i 一共有多少个字符,即 【i,right】,即 回文半径(单个字符的回文半径为1),pArr[i]=right-i+1;
2)int R表示回文右半径+1,即所有回文半径中能到达的最右位置+1,也就是上面那个公式里面最大的right+1
3)int C 表示最近一次更新R时,那个回文中心的位置。
3.接下来就是计算字符串的回文数组pArr[],计算时分两大情况:
3.1当前位置i不在回文右边界中,则向右边暴力扩张。
3.2当前位置i在回文右边界内(又分为三种情况):
3.2.1:i' (i关于c的对称点,即 i‘= 2c-i)的回文半径彻底在以C(回文中心)为中心所能到达的最右边界内,则R不用扩,i的回文半径为 i'的回文半径
例如:
3.2.2:i ’ 的回文半径在L外面,没包住,此时i的回文半径为i到R。
例如:
3.2.3:i'的回文半径压线,i与R之间的半径不用计算,但是不确定是否再向右扩
其实是有点模糊的,因为这个R其实是最右边界+1,所以要扣细节很多,不过背代码就完事了!!!!
因为回文半径这个屌玩意也是 right-i+1,所以这里R=right+1 刚好!!!
时间复杂度 O( n )
说下关键的记忆点:
1. 先变换字符串 插入#
2. 3个辅助变量
int index=-1;
int R= -1; // R:回文右半径+1, index 关于R的回文中心
vector<int> vec(new_str.size(),0); // vec: 存储回文半径3. 然后遍历新字符串, 关键代码:
vec[i]=R>i?min(vec[2*index-i],R-i):1; // 前面两个是在回文右半径里面的瓶颈 1是暴力扩 (包住了是瓶颈,没包住了是暴力扩)
4. 之后每人再给一次机会
5. 起点和长度怎么算:
return s.substr((res_dx-res_len+1)/2,res_len-1); // 注意起点是中心-半径+1 ,再÷2, 而长度是半径-1
二、算法实现
class Solution {
public:string longestPalindrome(string s) {if(s.size()<2)return s;int len=s.size();string new_str="#";for(int i=0;i<len;++i){new_str =new_str+s[i]+"#";} // 插入字符int index=-1;int R=-1; // R:回文右半径+1, index 关于R的回文中心int res_dx=0, res_len=0; // res_dx: 最长的中心 res_len: 最长的半径vector<int> vec(new_str.size(),0); // vec: 存储回文半径for(int i=0;i<new_str.size();++i){vec[i]=R>i?min(vec[2*index-i],R-i):1; // 前面两个是在回文右半径里面的瓶颈 1是暴力扩 while(i+vec[i]< new_str.size() && i-vec[i]>=0){if(new_str[i+vec[i]]==new_str[i-vec[i]])++vec[i];elsebreak;} //人人平等都再给你一次机会if(i+vec[i]>R){R=i+vec[i];index=i;} // 更新R indexif(vec[i]>res_len){res_len=vec[i];res_dx=i;} // 更新 res}return s.substr((res_dx-res_len+1)/2,res_len-1); // 注意起点是中心-半径+1 ,再÷2, 而长度是半径-1}
};
java:
public class Manacher {public static char[] manacherString(String str){ char[] charArr = CharArray();char[] res = new char[str.length()*2+1];int index = 0;for(int i=0;i<res.length;i++){//01 10&01 100&001res[i] = (i&1)==0?'#':charArr[index++];}return res;}public static int maxLcpsLength(String str){if(str==null||str.length()==0){return 0;}char[] charArr = manacherString(str);//扩充后的字符串int[] pArr = new int[charArr.length];//存储每个位置的最大回文长度int index = -1;//回文右边界中心 2*index-i是i位置关于index的对称点int pR = -1;//目前所能到达的最右边界int max = Integer.MIN_VALUE;for(int i=0;i!=charArr.length;i++){//对每个位置pArr[i] = pR>i?Math.min(pArr[2*index-i], pR-i):1;//起码回文的距离,看不用扩的区域是多少,是与之对称的点的回文半径限制了它还是它距离最右边界的距离限制了它while(i+pArr[i]<charArr.length&&i-pArr[i]>-1){//如果在范围内则往外扩if(charArr[i+pArr[i]]==charArr[i-pArr[i]]){pArr[i]++;}else{break;}}if(i+pArr[i]>pR){pR = i+pArr[i];index = i;}max = Math.max(max, pArr[i]);}return max-1;}public static void main(String[] args) {//String str = "abc1234321ab";String str = "1213121";System.out.println(maxLcpsLength(str));}
}
c++:
本文发布于:2024-01-28 10:02:18,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/17064073546632.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |