算法介绍
马拉车算法是用来在一个字符串中寻找最长回文串(正着读和反着读都相同的字符串)的一种算法。
该算法运用了动态规划的思想,将寻找最长回文串算法的时间复杂度降低到了线性。
算法原理
对于一个字符串要判断它是否为回文串要分为字符串长度为奇数或者偶数两种情况,为了简化做法,我们进行如下的操作:
- 在字符串的两端和每两个字符中间添加一个 ‘#’ (或者任意一个一定不会在字符串中出现的字符, 通常就是 ‘#’ 啦)
- 再在字符串的开始和结尾放置字符串开始和结束的标识符。
上述操作后拓展出来的字符串的长度一定是奇数。
例如:
abcd 拓展后变为 ^#a#b#c#d#$
abc 拓展后变为 ^#a#b#c#$
要将算法的复杂度降低到线性,我们就要用到动态规划的思想。
这里我们定义了一个dp数组,其中dp[i]定义为以下标为i的字符作为回文串中心的回文半径 (从中心元素到左边界或右边界的字符长度) 的大小。
这里举个例子
s[] = "abac"
str[] ^ # a # b # a # c # $
dp[] 1 2 1 4 1 2 1 2 1
我们得出dp数组后问题就可以解决了,所以接下来就是要着力求解dp数组了。
动态规划的核心便是通过之前的计算来简化之后要进行的计算,我们这里再引入两个量来实现这个过程:
int right; // 在计算过程中用于记录寻找到所有子回文串中右边界的下标
int pos; // 用于记录右边界就是right的子回文串的下标
接下来就是算法的关键了我们要逐次求出dp[i]的值:
我们在计算dp[i]时首先考虑这个值之前是否计算过,这里要充分利用回文串的特点。
----------------------------------------------------
^ ^ ^
|<--------------------|-------------------->|
left pos right
如果 i 在 pos 和 right 之间,则可以寻找 i 关于 pos 的对称点,这时可以先假设dp[i] = dp[2*pos-i]
,这个假设并不在任何情况下都成立:
如果中心为 2*pos-i 的回文串覆盖的区域左边界超过了left,则这个回文串中存在一些可能不在中心为 i 的回文串中的元素,这种情况下dp[i] = right-i
将上述讨论的情况结合起来即可得到:
dp[i] = min(dp[2*pos-i], right-i)
如果 i > right
那我们就用暴力的方法求解dp[i]即可
算法实现
int Manacher(char* s) {
int len = strlen(s+1);
char str[2*len+10];
int dp[2*len+10];
int cur = 0;
str[0] = '^';
for(int i = 0; i < len; ++i) {
str[++cur] = '#';
str[++cur] = s[i];
}
str[++cur] = '#', str[++cur] = '$';
int right = 0, pos = 0;
for(int i = 1; i <= cur; ++i) {
if(i < right)
dp[i] = min(dp[2*pos-i], right-i);
else
dp[right=i] = 1;
while(str[i-dp[i]] == str[i+dp[i]])
++dp[i];
if(i+dp[i] > right)
right = i+dp[i], pos = i;
}
return *max_element(dp+1, dp+cur+1)-1;
}
特别感谢
感谢我女朋友对我的大力支持😘。