Manacher
算法是一种回文串查找算法,专门用于处理查找字符串中的回文子串操作。Manacher
算法是基于暴力解法优化而来的,所以在阅读正式的算法之前,需要先了解暴力解法的思路和过程。这里我们采取从中间向两边扩散的方式,动态查找可能存在的最大回文串。
例如以0
下标位置的为中心的最长回文子串为a
以1
下标位置的为中心的最长回文子串为aba
以2
下标位置的为中心的最长回文子串为a
但是这种求解过程有一定局限性,它无法判断长度为偶数的回文串
所以如果我们需要同时判断偶数个的子串,就需要从两个值之间的位置出发判断
这里的选择,是通过直接添加补间字符的方式来解决这个问题。
例如:
这样的话我们就可以判断偶数串的回文情况。
至于为何前后各额外添加了一个#
,则是为了后面方便计算使用。
重构字符串代码:
static void manacherString(const string& str, string& s){s.clear();s.reserve(str.size() * 2 + 1);s.push_back('#');for (char c : str){s.push_back(c);s.push_back('#');}}
while
循环中的逻辑while
循环的判断条件是查看是否越界,if
语句用于判断边界上的两个字符是否相等。r
,然后进入while
循环,只要while
循环不跳出,半径就自增,通过这种方式来查找最大半径。(0
位置不用看,自己和自己必定相等)maxValue - 1
的原因 static int maxLcpsLength(const string& str){if (str.empty()) return 0;string mStr;manacherString(str, mStr);int maxValue = 0;for (int i = 0; i < mStr.size(); i++){int r = 1;// 找到以i 为中点的最大回文子串while (i + r < mStr.size() && i - r > -1){// 不相等就跳出if (mStr[i + r] != mStr[i - r]) break;//相等就扩大r++;}// 更新最大值maxValue = max(r, maxValue);}return maxValue - 1;}
这个方法还是相对而言比较简单的,而Manacher
就是在这个解法上进行优化,过滤了很多重复计算过的优化算法
Manacher
算法有了以上的知识作为基础,我们在来说Manacher
算法的原理,它其实是一种动态规划的思想
R
,已知最远右边界中心点C
。C
点时,发现它的回文半径可以衍生到R
位置,是目前遍历过的里面最远的,那么就更新C
和R
的信息,直到发现更远的边界下标为止。i'
关于中心点C
与当前位置i
对称i'
位置的回文信息i'
位置上的最大回文子串半径可以帮助我们过滤一定的特殊情况运算i
在R
范围以内时,我们可以利用已知的回文串对称的性质,将i
在R
范围以内的运算全部过滤掉.i
位置越过了R
边界范围,此时i'
也在R'
范围之外i
位置没有越过R
边界范围时,若i'
位置的最大回文子串在已知最远回文串以内,即i'
位置最大回文串左边界大于R'
i
位置没有越过R
边界范围时,若i'
位置的最大回文子串在已知最远回文串边界上,即i'
位置最大回文串左边界等于R'
i
位置没有越过R
边界范围时,若i'
位置的最大回文子串越过了已知最远回文串,即i'
位置最大回文串左边界小于R'
i
位置的最大回文串i'
在R' ~ R
以内,且边界不在R'
上,所以i'
最大回文串两边的元素一定不相等,且这两个元素都在R' ~ R
以内,由此可得,i
的最大回文串一定与i'
的最大回文串相等。R
范围以外不存在以i
为中心,更大的回文串。这种情况下,可以过滤半径在R' ~ R
范围以内的运算i'
最大回文串越界,又由于R' ~ R
范围两边的元素一定不相等,所以i'
的最大回文串在R'
范围外的元素与i
在R
界外的元素一定不相等,所以这种情况下R' ~ R
范围外也不存在以i
位置为中心更大的回文串。由于上述分析可得:我们最多可以过滤的运算数量有3种情况
i'
个R - i
个,即i' - R'
个基于上述理论,我们可以对之前的算法进行优化,下面给出代码
class Solution
{static void manacherString(const string& str, string& s){s.clear();s.reserve(str.size() * 2 + 1);s.push_back('#');for (char c : str){s.push_back(c);s.push_back('#');}}
public:static int maxLcpsLength(const string& str){if (str.empty()) return 0;string mStr;// 重构字符串manacherString(str, mStr);int* mArr = new int[mStr.size()];for_each(mArr, mArr + mStr.size(), [](int ele) { ele = 0; }); int C = -1;int R = -1;int max = 0;for (int i = 0; i < mStr.size(); i++){// 找到可以跳过的扩充区间mArr[i] = R > i ? min(mArr[2 * C - i], R - i) : 1;// 找到以i 为中点的最大回文子串while (i + mArr[i] < mStr.size() && i - mArr[i] > -1 && mStr[i + mArr[i]] == mStr[i - mArr[i]]){//相等就扩大mArr[i]++;}// 更新最大边界if (i + mArr[i] > R){R = i + mArr[i];C = i;}// 更新最大值(也可以值即记录值)max = mArr[max] > mArr[i] ? max : i;}return mArr[max] - 1;}};
下一篇:虚拟化平台安装并升级显卡驱动