1 Manacher算法
Manacher算法,又叫“马拉车”算法,可以在时间复杂度为O(n)的情况下求解一个字符串的最长回文子串长度的问题。
2 字符串处理
假设求解的字符串是abcc,要在其前后插入特殊字符(例如#),将其转化为#a#b#c#c#c#
2.1 最大回文半径
我们用 f(i) 来表示以字符串的第 i 位为回文中心,可以拓展出的最大回文半径,那么 f(i) - 1 就是以 i 为中心的最大回文串长度。为什么呢?通常长度等于2倍的最大半径再减去自身重复计算的长度1,即2*f(i) - 1,但是因为我们插入了特殊字符#,一半的字符是特殊字符是不应该算进来的,故而实际长度等于f(i) - 1。
此时以字符串的第 i 位为回文中心,可以拓展出的最大回文右端点r_m为$i + $f[$i] - 1;
2.2 状态转移方程
当我们计算f(i)时候,就两种情况
情况一:
i <=r_m时候,说明此时字符串的第i位被包含在i_m为中心可以拓展出的最大回文内,例如下面例子中下标7,那么此时我们不用再次中心拓展,因为此时该位置最起码能和其以i_m为中心的对称点f(2*i_m - i)所能拓展出的最大回文相等,毕竟在同一个最大回文嘛,这也是Manacher 算法的核心思想,记录其对称位置的最大回文半径,那么此位置一定大于等于其对称位置的最大回文半径,我个人理解是有点动态规划的思想,已经拓展过的,不必再次拓展,只是保存的不是f(i),而是i的对称点f(2*i_m - i),但是又因为对称点可能向左有拓展,这边未必能向右拓展,所以对应的f(i) = min( r_m - i + 1, f(2*i_m - i) ),然后再去中心拓展,即状态转移方程就是:
情况二:
i >r_m,直接f(i) = 1
2.3 ABAAB实例实例
下面是ABAAB实例的每一步处理例子:
在下标为
7处,其以i=5为中心的对称点下标为2*i_m - i = 2*6 - 7 = 5,对应的最大半径为min( f(5), r_m - i + 1 ) = min( f(5), 10 - 7 + 1) = min(2, 4) = 2,则7至少在[6,8]之间是回文的,然后左边从i - f(i) = 7 - 1 = 6,右边从i + f(i) = 7 + 1 = 8,向两边拓展,拓展失败
3 PHP 语言描述
public function countSubstrings($s)
{
//填充特殊字符
$arr = str_split($s, 1);
$s = implode('#', $arr);
$s = '#'.$s.'#';
$len = strlen($s);
$i_m = 0;
$r_m = 0;
$ans = 0;//不同回文子串的个数,因为加入了特殊字符,故而是$f[$i]/2,向下取整
$f = [];
$res = [];//每个位置的最大回文子串集合
for ($i=0; $i < $len; $i++) {
//初始化
if ($i <= $i_m && isset($f[2*$i_m - $i])) {
$f[$i] = min($r_m - $i + 1, $f[2*$i_m - $i] );
}else{
$f[$i] = 1;
}
//中心拓展
while ($i-$f[$i] >=0 && $i+$f[$i] < $len && $s[$i-$f[$i]] == $s[$i+$f[$i]]) {
$f[$i]++;
}
$res[] = substr($s,$i - $f[$i] + 1,2*$f[$i] - 1);
//更新$i_m $r_m
if ($i + $f[$i] - 1 > $r_m) {
$i_m = $i;
$r_m = $i + $f[$i] - 1;
}
$ans += floor($f[$i]/2);
}
var_dump($s,$f,$res);
return $ans;
}