📜  后缀树应用6 –最长回文子串(1)

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

后缀树应用6 – 最长回文子串

简介

回文串是一个正着读和倒着读都相同的字符串,比如"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是原字符串的长度。这是一种较为高效的解决方案,此算法在实际应用中也有一定的适用性。