1.回文 :正着念和反着念都是一样的,就是可以对称。
2.暴力解不是很好,因为有奇数和偶数长度的串。奇数长度可以方便地扩展回文,但是偶数不便扩展。
3.使用技巧:在字符串的每个字符的两边都穿插特殊标记符号(任何符号都可以:因为虚轴不会和实轴比对)。这就使所有的字符串都变成了奇数长度。
4.例如:11311 ---> #1#1#3#1#1# -->1 3 5 3 1 11 1 3 5 3 1 最长回文就是最大值➗2。即11311的最大回文数是11/2=5。
5.前提概念:
回文直径:以某个字符为中心,扩展出来的回文长度。
回文半径:回文直径的一半
回文半径数组:从0开始,从左往右开始求回文半径,将每个位置的回文半径存入回文半径数组。
回文右边界:从左往右扩,右边界是当前字符为中心扩展回文右边最远的位置。
回文右边界中心:第一次到达右边最远位置的中心
可能性1:i位置不在回文右边界里面。例如:#1#2#1# 求解第一个#时回文右边界是-1,不在回文右边界里面,更新回文右边界到“#”位置;扩展第一个“1”时,回文右边界在“#”位置,所有右边界更新为“1”位置。
可能性2:i位置在回文右边界内;带求解的i在回文右边界中心c的对称i*的范围在c的回文范围内。i的回文半径就是i*的回文半径。
可能性3:i位置在回文右边界内;带求解的i在回文右边界中心c的对称i*的范围不在c的回文范围内。i的回文半径就是i到R的距离(最远右边界)。
可能性4:i位置在回文右边界内;带求解的i在回文右边界中心c的对称i*的回文半径和以c为回文半径压线(重合)。i到R这部分必定是回文,但是往右边的下一位是不是,就得继续判断,不确定。
可能性:根据上面分析的4种情况,可以综合出以下内容。
CASE1:i在R外 ----->暴力扩展
CASE2:i在R内
A)i*回文在L,R内 ---->直接得解:i的回文半径就是i*的回文半径
B)i*回文在L,R外 ---->直接得解:i是(R-i)
C)i*回文左边界与L重合,则需要确定R的右边继续查找是否符合。
算法复杂度:O(N)
上面可能性的示例:
注释:{ }:L,R
[ ]:i或者i*的范围
加黑字母:c
加黑斜体字母:i或者i*
下划线为i的回文直径
CASE2(A): t {F [aba] ktk [aba] F} s
CASE2(B): [ab {cEcba] tttabcEc} F
CASE2(C): {[abcEcba]FFF[abcEcba]}? -->继续判断“?”是否等于“F”
代码展示:
def manacher_string(str): string = "#" for i in str: string = string + i + "#" return string # a = manacher_string("dfdfsd") # print(a) def manacher(str): arr = [] # 回文半径数组 str = manacher_string(str) C = -1 # C是回文中心 R = -1 # R是回文右边界 max = 0 # 更新最大回文数 for i in range(len(str)): # 如果R>i,意味着符合CASE2中的A:i*在边界内-->arr[i] = arr[2*c-i], # B:i*在边界外-->arr[i] = R-i # C:i*与边界重合-->arr[2*c-i] == R-i # 当i >= R时,需要暴力扩。 arr.append(min(arr[2*C-i], R-i))if R > i else arr.append(1) while i + arr[i] < len(str) and i - arr[i] > -1: # i - arr[i] 左极限是第一个元素的情况 0-1=-1 if str[i+arr[i]] == str[i-arr[i]]: arr[i] = arr[i] + 1 else: break if i + arr[i] > R: # 更新最大右边界和中心 R = i + arr[i] C = i max = arr[i] if arr[i] > max else max return max - 1 a = manacher("abxxba") print(a)