0%

Manacher

Manacher算法

解释: 获得一个字符串的最大回文子串长度

概念:

  1. 回文直径: 回文字符串的长度
  2. 回文半径: 回文字符串长度的一半
  3. 回文右边界: 所有回文字符串中, 回文半径能到达的最右边的索引
  4. 回文右边界的中心: 第一次到达回文右边界的时候的回文中心

算法思路:

字符串预处理: 在每个字符两边都加一个标准字符. 如:ABC变成#A#B#C#. 原因: 防止偶数回文的影响

  1. 遍历每个字符位置, 开始向两边扩
  2. 如果当前位置没在回文右边界, 暴力扩(例如: 字符串ABACD, 运行到CD的位置时)
  3. 如果当前位置在回文右边界中, 找到当前位置关于回文中心的对称点
    1. 如果对称点的回文半径在C(C为回文右边界的中心)的左边(即对称点的回文直径返回内的字符都在C的回文半径中), 那么当前位置的回文半径就是这个对称点的回文半径(例如: 字符串ABCDEFGFEDCBA, 运行到FEDCBA位置时)
    2. 如果对称点的回文半径超过了C(即对称点的回文直径中的值存在超过回文中心点直径的范围)(例如: 字符串ABCBADABC, 运行到C位置的时候), 此时回文半径的长度是当前位置到回文右边界的长度
    3. 对称点的回文左边界和回文右边界中心的回文左边界相同, 此时需要从回文右边界开始向右扩

时间复杂度:

  • 时间复杂度的就是回文右边界移动的范围, 所以时间复杂度为O(N)

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public static Tuple<int, String, int> Manacher(String str, char ch = '#')
{
if (String.IsNullOrEmpty(str))
{
return null;
}
StringBuilder sb = new StringBuilder();
sb.Append($"{ch}");
foreach (char item in str)
{
sb.Append($"{item}{ch}");
}
int[] radius = new int[sb.Length];
int moreRight = -1, center = -1;
for (int i = 0; i < radius.Length; i++)
{
if (i > moreRight)
{
int tmp = 1;
int length = 0;
while (i - tmp >= 0 && i + tmp < radius.Length && sb[i - tmp] == sb[i + tmp])
{
length++;
tmp++;
}
radius[i] = length;
center = i;
moreRight = i + tmp - 1;
}
else
{
int moreLeft = 2 * center - moreRight, symmetry = 2 * center - i;
if (symmetry - radius[symmetry] > moreLeft)
{
radius[i] = radius[symmetry];
}
else if (symmetry - radius[symmetry] < moreLeft)
{
radius[i] = moreRight - i;
}
else
{
int length = moreRight - i;
int tmp = length + 1;
while (i - tmp >= 0 && i + tmp < radius.Length && sb[i - tmp] == sb[i + tmp])
{
length++;
tmp++;
}
int newRight = i + tmp - 1;
if (newRight > moreRight)
{
moreRight = newRight;
center = i;
}
radius[i] = length;
}
}
}
int maxValue = radius[0], maxIndex = 0;
for (int i = 1; i < radius.Length; i++)
{
if (maxValue < radius[i])
{
maxValue = radius[i];
maxIndex = i;
}
}
string source = sb.ToString().Substring(maxIndex - maxValue, 2 * maxValue + 1);
StringBuilder target = new StringBuilder();
for (int i = 1; i < source.Length; i += 2)
{
target.Append($"{source[i]}");
}
return new Tuple<int, string, int>(maxValue, target.ToString(), (maxIndex - maxValue) >> 1);
}