【问题标题】:Longest palindrome in a string using suffix tree使用后缀树的字符串中的最长回文
【发布时间】:2011-10-26 00:07:56
【问题描述】:

我试图在一个字符串中找到最长的回文。蛮力解决方案需要 O(n^3) 时间。我读到有一个使用后缀树的线性时间算法。我熟悉后缀树并且很乐意构建它们。你如何使用构建的后缀树来找到最长的回文。

【问题讨论】:

  • 暴力破解只需要 O(N^2) 时间。
  • 它不是上述链接的副本。试着理解这个问题。上述问题中的答案充其量给出了一个没有后缀树的超线性算法。我正在研究使用后缀树的 O(n) 解决方案。
  • 那么你的问题更像是“假设我建立了一个后缀树 - 我如何使用这棵树来查找回文?”它似乎相关,但不是链接问题的重复。
  • 我不能投票重新开放,但它不是重复的!!!这是一个特定的面试问题,intererer 首先期望后缀树(与其他答案无关)以及对其工作原理的直观解释。不是 100 行代码,也不是关于如何尽快在 100 亿 ACGT 的基因序列中找到回文的完整理论 (johanjeuring.blogspot.com/2007/08/finding-palindromes.html) ...
  • 在 cmets 中查看对话后重新打开。

标签: algorithm palindrome suffix-tree


【解决方案1】:

晚了几年……

假设 s 是原始字符串,rs 反转。我们还假设我们已经使用s 完全构建了一个后缀树ST

我们的下一步是检查r 的所有后缀与ST。对于 r 的每个新后缀,我们将保持与树中预先存在的后缀(即 s 的后缀之一)成功匹配的第一个 k 字符的计数。

例如,假设我们匹配来自r 的后缀"RAT",而s 包含一些以"RA" 开头的后缀,但是没有匹配“RAT”。当我们最终不得不放弃对最终字符 “T” 的希望时,k 将等于 2。我们将r 的后缀的前两个字符与s 的后缀的前两个字符匹配。我们将调用我们到达的这个节点n

现在,我们如何知道何时找到了回文? 通过检查n下的所有叶子节点。

在传统的后缀树中,每个后缀的起始索引存储在该后缀分支的叶节点上。在我们上面的示例中,s 可能包含一堆以 "RA" 开头的后缀,每个后缀都以 n 的叶节点后代中存在的一个索引开始。

让我们使用这些索引。

如果我们将R 的子字符串之一的k 字符与ST 中的k 字符匹配,这意味着什么?好吧,这只是意味着我们发现了一些颠倒的字符串。但是,如果R 中的子字符串开始位置等于S 中的匹配子字符串加上k,这意味着什么?是的,这意味着s[i] through s[i+k]s[i+k] through s[i] 读取相同!因此,定义我们已经找到了一个大小为k 的回文。

现在,您所要做的就是在迄今为止找到的最长回文上保留一个标签,并在函数结束时将其返回。

【讨论】:

  • 你能详细说明为什么之前的答案是错误的吗?
  • 这是一个非常简单的解决方案,似乎是正确的,但为什么其他答案如此复杂?甚至是@zsoltsafrany 的作者,他描述了 Skiena 书中的解决方案。
  • 我对所有错误答案的投票感到有点震惊。人们在投票之前会在这里思考吗?
  • 终于有了一个正确的后缀树算法。很好的解释。
  • 我想知道这个算法的复杂性。构造一个后缀树:O(n)。枚举反向后缀:O(n)。将每个反向后缀放入树中:O(n)。所以总的来说,这将产生 O(n+n*n)。对吗?
【解决方案2】:

来自Skiena - The Algorithm Design Manual的简单简短的解释

找到S中最长的回文[使用后缀树] - 回文是一个字符串,如果字符顺序颠倒,则读取相同,例如女士。为了在字符串 S 中找到最长的回文,构建一个包含 S 的所有后缀和 S 的反转的单个后缀树,每个叶子都由它的起始位置标识。回文由这棵树中具有来自同一位置的正向和反向子节点的任何节点定义。

【讨论】:

    【解决方案3】:

    DP解决方案:

    int longestPalin(char *str)
    {
        n = strlen(str);
        bool table[n][n]l
        memset(table, 0, sizeof(table));
        int start = 0;
    
        for(int i=0; i<n; ++i)
            table[i][i] = true;
        int maxlen = 1;
    
        for(int i=0; i<n-1; ++i)
        {
            if(str[i] == str[i+1])
            {
                table[i][i] = true;
                start = i;
                maxlen = 2;
            }
        }
    
        for(int k=3; k<=n; ++k)
        {
            for(int i=0; i<n-k+1; ++i)
            {
                int j = n+k-1;
                if(str[i] == str[j] && table[i+1][j-1])
                {
                    table[i][j] = true;
                    if(k > maxlen)
                    {
                        start = i;
                        maxlen = k;
                    }
                }
            }
        }
        print(str, start, start+maxlen-1);
        return maxlen;
    }
    

    【讨论】:

    • 问题是“使用后缀树”而不是 DP。
    【解决方案4】:

    可以通过这种方式找到线性解决方案::

    先决条件:

    (1).你必须知道如何在 O(N) 或 O(NlogN) 时间内构造后缀数组。

    (2).您必须知道如何找到标准 LCP 阵列,即。相邻后缀 i 和 i-1 之间的 LCP

    即。 LCP[i]=LCP(排序数组后缀i,排序数组后缀i-1)为(i>0)。

    S 为原始字符串,S' 为原始字符串的反面。 让我们以 S="banana" 为例。 然后它的反向字符串 S'=ananab。

    第 1 步:连接 S + # + S' 得到 String Str ,其中 # 是原始字符串中不存在的字母。

        Concatenated String Str=S+#+S'
        Str="banana#ananab"
    

    第二步:现在构造字符串Str的后缀数组。

    在本例中,后缀数组为:

    Suffix Number   Index   Sorted Suffix
    0               6       #ananab
    1               5       a#ananab
    2               11      ab
    3               3       ana#ananab
    4               9       anab
    5               1       anana#ananab
    6               7       ananab
    7               12      b
    8               0       banana#ananab
    9               4       na#ananab
    10              10      nab
    11              2       nana#ananab
    12              8       nanab
    

    请注意,后缀数组是一个整数数组,它按字典顺序给出字符串后缀的起始位置。因此,保存起始位置索引的数组是后缀数组。

    SuffixArray[]={6,5,11,3,9,1,7,12,0,4,10,2,8};

    第 3 步:由于您已经成功构建了后缀数组,现在找到相邻后缀之间的最长公共前缀

    LCP between #ananab        a#ananab          is :=0
    LCP between a#ananab       ab                is :=1
    LCP between ab             ana#ananab        is :=1
    LCP between ana#ananab     anab              is :=3
    LCP between anab           anana#ananab      is :=3
    LCP between anana#ananab   ananab            is :=5
    LCP between ananab         b                 is :=0
    LCP between b              banana#ananab     is :=1
    LCP between banana#ananab  na#ananab         is :=0
    LCP between na#ananab      nab               is :=2
    LCP between nab            nana#ananab       is :=2
    LCP between nana#ananab nanab                is :=4
    

    因此 LCP 数组 LCP={0,0,1,1,3,3,5,0,1,0,2,2,4}。

    其中 LCP[i]=后缀 i 和后缀 (i-1) 之间的最长公共前缀长度。 (对于 i>0)

    第 4 步:

    现在你已经构建了一个 LCP 数组,使用以下逻辑。

        Let the length of the Longest Palindrome ,longestlength:=0 (Initially)
        Let Position:=0.
        for(int i=1;i<Len;++i)
        {
            //Note that Len=Length of Original String +"#"+ Reverse String
            if((LCP[i]>longestlength))
            {
                //Note Actual Len=Length of original Input string .
                if((suffixArray[i-1]<actuallen && suffixArray[i]>actuallen)||(suffixArray[i]<actuallen && suffixArray[i-1]>actuallen))
                {
                     //print :Calculating Longest Prefixes b/w suffixArray[i-1] AND  suffixArray[i]
    
    
                    longestlength=LCP[i];
                  //print The Longest Prefix b/w them  is ..
                  //print The Length is :longestlength:=LCP[i];
                    Position=suffixArray[i];
                }
            }
        }
        So the length of Longest Palindrome :=longestlength;
        and the longest palindrome is:=Str[position,position+longestlength-1];
    

    执行示例::

        actuallen=Length of banana:=6
        Len=Length of "banana#ananab" :=13.
    
    Calculating Longest Prefixes b/w a#ananab AND  ab
    The Longest Prefix b/w them  is :a 
    The Length is :longestlength:= 1 
    Position:= 11
    
    
    
    
    Calculating Longest Prefixes b/w ana#ananab AND  anab
    The Longest Prefix b/w them  is :ana
    The Length is :longestlength:= 3 
    Position:=9
    
    
    
    Calculating Longest Prefixes b/w anana#ananab AND  ananab
    The Longest Prefix b/w them  is :anana
    The Length is :longestlength:= 5 
    Position:= 7
    
    So Answer =5.
    And the Longest Palindrome is :=Str[7,7+5-1]=anana
    

    只需记下::

    第 4 步中的 if 条件基本上是指,在每次迭代(i) 中,如果我取后缀 s1(i) 和 s2(i-1) 则 ,"s1 必须包含 # 和s2 不能包含 #" OR "s2 必须包含#,s1 不能包含#"。

     |(1:BANANA#ANANAB)|leaf
    tree:|
         |     |      |      |(7:#ANANAB)|leaf
         |     |      |(5:NA)|
         |     |      |      |(13:B)|leaf
         |     |(3:NA)|
         |     |      |(7:#ANANAB)|leaf
         |     |      |
         |     |      |(13:B)|leaf
         |(2:A)|
         |     |(7:#ANANAB)|leaf
         |     |
         |     |(13:B)|leaf
         |
         |      |      |(7:#ANANAB)|leaf
         |      |(5:NA)|
         |      |      |(13:B)|leaf
         |(3:NA)|
         |      |(7:#ANANAB)|leaf
         |      |
         |      |(13:B)|leaf
         |
         |(7:#ANANAB)|leaf
    

    【讨论】:

    • 我必须说Amazing Explanation..感谢它帮助我解决了Spoj问题LPS
    • 谢谢你的解释,虽然对于这个例子:1234xaba4321,这个算法会选择1234还是4321而不是aba作为结果?
    • @poiu2000 来自文字:步骤4中的if条件基本上是指,在每次迭代(i)中,如果我取后缀s1(i)和s2(i-1),那么,“s1必须包含 # 且 s2 不得包含 # " OR "s2 必须包含 # 而 s1 不得包含 # "
    • 严格来说,这个答案是使用后缀数组而不是后缀树,它并不能真正回答问题。
    【解决方案5】:

    我相信你需要这样做:

    y1y2 ... yn 是你的字符串(其中 yi 是字母)。

    创建Sf的广义后缀树 = y1y2 ... yn$Sr = yn yn - 1 ... y1#(颠倒字母,为Sf($)和Sr选择不同的结束字符em> (#))... 其中 Sf 代表 "String, Forward"Sr 代表“字符串,反向”

    对于Sf中的每个后缀i,找出后缀为n - i + 1的最低共同祖先 em> 在 Sr.

    从根到这个最低的共同祖先是回文,因为现在最低的共同祖先代表这两个后缀中最长的共同前缀。回想一下:

    (1) 后缀前缀子串

    (2) 回文是一个与其反向相同的字符串。

    (3) 所以一个字符串中最长包含的回文正好是这个字符串的最长公共子串及其反串。

    (4) 因此,字符串中包含的最长回文恰好是字符串与其反向之间的所有后缀对中最长的公共前缀。这就是我们在这里所做的。

    示例

    我们来看看banana这个词。

    Sf = 香蕉$

    Sr = ananab#

    下面是SfSr的广义后缀树,其中末尾的数字每条路径的索引是对应后缀的索引。有一个小错误,Blue_4 的父级的所有 3 个分支共有的 a 应该在其进入边缘,在 n 旁边:

    树中最低的内部节点是该字符串的最长公共子字符串及其反向。因此,查看树中的所有内部节点,您会发现最长的回文。

    最长的回文位于 Green_0 和 Blue_1 之间(即 bananaanana)并且是 anana


    编辑

    我刚刚发现 this paper 可以回答这个问题。

    【讨论】:

    • 您好,您是如何制作出如此精美的图片的?哪个工具?
    • 在上面的例子中,为什么“ana”是答案?不应该是“anana”吗?
    • @ricky-boby :你确定你的第三和第四个索赔?例如,字符串:xyztuvananakvutzyx 及其反向 xyztuvkananavutzyx 以 xyztuv 作为最长公共前缀,但 xyztuv 不是平原。
    • 请修正这个答案。我想你只需要检查回文的长度加上正向字符串中的起始索引和反向字符串中的起始索引是否等于原始字符串的长度。
    • -1。正如 CEGRD 指出的那样,这个答案是错误的。主张 3 和 4 不成立。您应该认真修复或删除此答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-10-06
    • 2018-12-02
    • 2016-02-14
    • 1970-01-01
    • 2019-10-11
    • 2013-04-30
    • 2011-05-27
    相关资源
    最近更新 更多