📜  使用回文树的最长回文子串套装3(1)

📅  最后修改于: 2023-12-03 15:36:38.856000             🧑  作者: Mango

使用回文树的最长回文子串套装3

简介

回文树是一种数据结构,其主要用途是在字符串中查找回文子串。最长回文子串是在一个字符串中找到最长的回文子串。通过使用回文树,可以在线性时间复杂度内找到最长回文子串。

在本套装中,我们介绍如何使用回文树来找到最长回文子串。

算法流程
  1. 构建回文树。回文树是一个广义后缀树,它的每一个节点代表一个回文串。每一个边代表一个字符。
  2. 求出每一个节点的长度,这个长度代表以该节点为中心的回文串的长度。
  3. 从回文树中找到最长的回文子串。
    1. 找到长度最长的节点。
    2. 沿着该节点的父节点一直找到根节点。
    3. 将这些节点代表的回文串连接起来就是最长回文子串。
代码实现

回文树的实现需要先构建样例字符串的回文树。

Markdown::

s = 'abcbab'
n = len(s)

# 将样例字符串翻转,单独保存翻转后的字符串t
t = s[::-1]

# 初始化回文树的根节点p[0]和p[1],p[0]表示长度为0的回文串,p[1]表示长度为-1的虚拟节点。
p = [-1] * (2 * n)

# 初始化回文树的父节点par
par = [0] * (2 * n)

# 初始化回文树的节点长度len
plen = [0] * (2 * n)

# 初始化回文树的节点出现次数cnt
cnt = [0] * (2 * n)

# 初始化回文树的节点转移
nxt = [{} for _ in range(2 * n)]

# 初始时回文树中只有两个节点,一个代表长度为0的回文串,一个代表虚拟节点。
sz = 2
par[0], par[1] = 1, 0
plen[0], plen[1] = 0, -1
cnt[0], cnt[1] = n + 1, 1

# 依次向回文树中添加字符,每次添加字符时,都从上一次添加的节点开始向上匹配,
# 直到找到第一个节点,匹配失败,则需要在此基础上添加一个新的节点。
last = 1
for i in range(n):
    cur = last
    while s[i - plen[cur] - 1] != s[i]:
        cur = par[cur]
    if c := nxt[cur].get(s[i]):
        last = c
        cnt[last] += 1
    else:
        sz += 1
        last = sz
        plen[last] = plen[cur] + 2
        nxt[cur][s[i]] = last
        par[last] = cur
        cnt[last] = 1
        if plen[last] == 1:
            cnt[last] = n + 1
        cur = par[cur]
        while cur and s[i - plen[cur] - 1] != s[i]:
            cur = par[cur]
        if cur:
            par[last] = nxt[cur][s[i]]
        else:
            par[last] = 1

将根节点和虚拟节点各自删掉。我们将回文树上的每个节点拆分为两个节点,一个节点表示以该节点为中心的奇数长度的回文串,一个节点表示以该节点为中心的偶数长度的回文串。

Markdown::

# 将根节点和虚拟节点分别删除
del p[0], p[0]
del par[0], par[0]
del plen[0], plen[0]
del cnt[0], cnt[0]
del nxt[0], nxt[0]

del p[0], p[0]
del par[0], par[0]
del plen[0], plen[0]
del cnt[0], cnt[0]
del nxt[0], nxt[0]

# 将回文树上的每个节点拆分为两个节点,一个节点表示以该节点为中心的奇数长度的回文串,
# 一个节点表示以该节点为中心的偶数长度的回文串。
for i in range(sz - 1, 1, -1):
    if plen[i] & 1:
        p[i * 2 - 1] = p[i]
        p[i] = -1
    else:
        p[i * 2 - 2] = p[i]
        p[i] = -1

现在我们已经完成了回文树的构建和拆分,接下来就可以求出最长回文子串。

Markdown::

# 找到长度最长的节点
ans_node = max(range(2, sz), key=lambda i: plen[i])

# 沿着该节点的父节点一直找到根节点
ans = ''
while ans_node > 1:
    if p[ans_node] != -1:
        ans += s[plen[ans_node] // 2]
    ans_node = par[ans_node]

# 将这些节点代表的回文串连接起来就是最长回文子串
ans = ans[::-1] + ans

print(ans)
总结

通过使用回文树,我们可以在线性时间复杂度内找到一个字符串中的最长回文子串。回文树是一种广义后缀树,它包含了一个字符串中的所有回文串。通过对回文树进行处理,我们可以找到最长回文子串。

回文树的构建算法比较复杂,但是它具有较高的效率。在实际应用中,我们可以使用一些已经实现好的回文树库。