📅  最后修改于: 2023-12-03 15:07:28.664000             🧑  作者: Mango
回文串是一个正着读和倒着读都相同的字符串,比如"level"、"deified"等等。在字符串处理中,求解最长回文子串是一个经典问题。本文将介绍如何使用后缀树来实现求解最长回文子串的算法。
首先我们将原字符串翻转,然后将翻转后的字符串作为后缀树的构建基础。然后我们在后缀树中找到所有的节点,这些节点对应的字符串都是原字符串和翻转字符串中的某个后缀。对于每一个节点,我们可以利用后缀链接(构建后缀树时插入的边),找到和它回文的节点。
如果两个节点的公共前缀的length为x,那么可以得到到这两个节点的回文串的长度为2*x。因为最终所求的回文串是原字符串和翻转字符串中的一个公共子串,所以我们考虑如何从一对回文节点中推出原字符串中的回文子串。
由于我们是先将字符串翻转再构建后缀树,所以在构建后缀树时,每一个节点对应的字符串的开头都是原字符串的一个后缀。我们从每一个回文节点开始,向上遍历直到某一层为回文节点对应的字符串在翻转后的字符串中的结尾。我们因为得到了回文节点对应的树上路径上的所有节点,这条路径对应的字符串即为原字符串的一个回文子串的翻转。最后,我们仅需要将翻转后的子串再次翻转即可得到答案。
以下为Python代码实现:
class Node:
def __init__(self, start, end, link=None):
self.start = start
self.end = end
self.link = link
self.edges = {}
def __repr__(self):
return "Node(%d, %d)" % (self.start, self.end)
class SuffixTree:
def __init__(self, s):
self.s = s
self.s_len = len(s)
self.suffix_i = [-1] * self.s_len
self.nodes = [None] * (2 * self.s_len)
self.num_nodes = 0
self.curr_node = None
self.curr_edge = -1
self.curr_len = 0
self.remaining_suffix_count = 0
self.build()
def build(self):
self.nodes[0] = Node(-1, -1)
self.num_nodes += 1
self.curr_node = 0
for i in range(self.s_len):
self.add_prefix(i)
self.set_suffix_indices(self.curr_node, 0)
def add_prefix(self, suffix):
global last_parent_node
last_parent_node = None
while self.remaining_suffix_count > 0:
if self.curr_len == 0:
self.curr_edge = suffix # case1
if self.s[self.suffix_i[self.curr_node] + self.curr_len + 1] == self.s[suffix]:
self.curr_len += 1 # case2
if last_parent_node is not None:
last_parent_node.link = self.curr_node
break
split_end = self.suffix_i[self.curr_node] + self.curr_len
split_node = Node(self.suffix_i[self.curr_node], split_end, self.curr_node)
self.nodes[self.num_nodes] = split_node
self.curr_node.edges[self.s[split_node.start + self.curr_len]] = self.num_nodes
self.num_nodes += 1
if last_parent_node is not None:
last_parent_node.link = split_node
last_parent_node = split_node
self.remaining_suffix_count -= 1
if self.curr_node == 0:
self.curr_len -= 1
self.curr_edge = suffix - self.remaining_suffix_count + 1
else:
self.curr_node = self.nodes[self.curr_node].link
else:
self.curr_edge = suffix - self.remaining_suffix_count + 1
self.suffix_i[self.num_nodes] = suffix
self.remaining_suffix_count += 1
last_parent_node = None
def set_suffix_indices(self, node, leaf_depth):
if node.start != -1:
leaf_depth = node.end - node.start
for e in node.edges.values():
self.set_suffix_indices(self.nodes[e], leaf_depth + self.nodes[e].end - self.nodes[e].start)
if node.start == -1:
return
if node.link is not None:
slen = node.end - node.start
self.suffix_i[node.link] = self.suffix_i[node] - slen
node.end -= leaf_depth
def find_lcs(s, suffix_tree):
longest_common_substring = ""
longest_len = 0
nodes = suffix_tree.nodes
for i in range(1, suffix_tree.num_nodes):
node = nodes[i]
if node.link is None:
continue
if (node.end - node.start) > longest_len:
if ((node.start + node.end) // 2) <= len(s):
longest_common_substring = s[(node.start + node.end) // 2 - (node.end - node.start) // 2:
(node.start + node.end) // 2 + (node.end - node.start) // 2]
longest_len = node.end - node.start
return longest_common_substring[::-1] # reverse the result
def find_lps(s):
reversed_s = s[::-1]
t = SuffixTree(reversed_s + "#" + s)
return find_lcs(s, t)
if __name__ == '__main__':
assert find_lps("forgeeksskeegfor") == "geeksskeeg"
本文介绍了如何使用后缀树来实现求解最长回文子串的算法。该算法的时间复杂度为O(n),其中n是原字符串的长度。这是一种较为高效的解决方案,此算法在实际应用中也有一定的适用性。