对策字符串的最大长度
题目
输入一个字符串,输出该字符串中对称的子字符串的最大长度。比如输入字符串 “google”,由于该字符串里最长的对称子字符串是“goog”,因此输出 4。
解析
该题目比较直观的解法有:
- 暴力法:遍历所有可能的子串,然后判断子串是否为回文,故总得复杂度为O(n^3)
- 遍历字符串,以当前遍历为中心点,然后向两边扩展,直至扩展到不是回文,总复杂度为O(n^2)
还有后缀树也可以解,不过需要较大的空间,本文使用的是manacher算法:
Manacher最早发现了可以用O(n)的时间复杂度来解决该问题,所以这种方法称之为Manacher算法。
现在以查询字符串”abaab”为例,该算法为了将”aba”和”baab” 奇偶形式统一考虑,做了一个很精妙的预处理:每每两个字符串以及两端都加一个标志符号#,就形成了
“#a#b#a#a#b#”新字符串,那么”aba”则可以看做”#a#b#a#”,同时”baab”转为#b#a#a#b#”,就都为奇数形式了
假设当前的字符串长度为n,则添加的#个数为2*n+1
接下来需要添加一个辅助数组p,其中p[i]的值表示第i位上可以形成的最大回文半径(比如p[1]=2 因为#a#的半径为2),全部先列出的话为:
新串:# a # b # a # a # b #
p[]:1 2 1 4 1 2 5 2 1 2 1
其实我们可以通过p[i]的值就可以得到每个字串相应位置的回文长度
半径p[i]的回文长度为2*p[i]-1,而每个在新字符串上每个回文两端必定是#,也是也就是相当于对原回文做了#的添加操作,假设原回文串为x长度,根据上面的添加#计算方式得到添加后的回文串为2*x+1
故2*p[i]-1=2*x+1
可得x=p[i]-1
那么现在的问题就是相当于如何求出新字符串上p数组各个元素的值了,当然计算这个也不能靠暴力来算,不然就没意思了,大概我们了解到计算p[i]的时候 p[j]的值都是已经算出来了,同时回文串有对称性质,所以我们接下来可以这么看
现在要求p[5],根据P[3]=4可知,p[5]是包含在p[3]的回文串中,又因为回文的对称性,所以p[3]左侧一定存在一个字符串与p[5]一致,
其实这里就可以算出其对称串为2*3-5=1 也就是p[1],P[1]是已经算出的=2 所以p[5]也必定至少为2(这里要考虑P[3]回文的右界,不能超过)
所以此时p[5]只需要从p[3]和p[7]往两侧扩展即可,判断其存在的最大回文,这样就可以大大减少了重复计算
接下来借用别人的图来解释一下
对应上面的例子你可以将变量这么认为:i=5,id=3,j=1(通过2*id-i计算) 而mx=p[id]+4,其中id表示先前存在的能到最大右边的回文中心标记。。。
所以当mx>i的时候 就可以说明在计算i的时候 可以根据id找到左侧对应的点 这样就可以减少计算量
但是另一种情况就是p[j]的回文范围超越了p[id]的回文范围,那这样就不能完全使用p[j]的值了
此时p[i]已知的最大可确保回文的范围就是mx-i
综合上述:初始化p[i]=min(p[2*id-i],mx-i) 当mx>i
好,该算法的核心已经说明了接下来直接上代码:
代码
1 | /** |
测试的一些样例为
1 | public static void main(String[] args) |
结果:
baab
goog
asdxdsa
他们都说这个方法复杂度为O(n),应该也是,主要是比较maxId,其余的重复已经都是可以根据前面的结果算得
参考
- Manacher算法处理字符串回文
- Manacher算法:求解最长回文字符串,时间复杂度为O(N)
- july 编程之美 73题
本作品采用[知识共享署名-非商业性使用-相同方式共享 2.5]中国大陆许可协议进行许可,我的博客欢迎复制共享,但在同时,希望保留我的署名权