这是关于Jon Bentley的“Programming Pearls”中描述的最长的重复子串算法的问题。我记得,他们为输入字符串构建一个后缀数组,对后缀进行排序,并扫描它们。
看起来最昂贵的一步就是整理。如果我们使用比较排序,那么比较的数量是O(N * logN),其中N是输入字符串的大小。由于字符串比较为O(字符串长度),因此排序为O(N ^ 2)。
有意义吗?
因此,该算法在空间中为O(N ^ 2)和O(N)。可以做得更好吗?
答案 0 :(得分:3)
虽然你可以从后缀树构造一个后缀数组,但没有什么意义。它将消除后缀数组的主要优点(除了一般简单性):微小的内存消耗。
有一种简单的方法可以在O(n*logn)
时间内对后缀数组进行排序。 (因此,它经常用于各种算法竞赛,作为复杂后缀尝试的替代方案)
基本思路如下。我们会分阶段对后缀进行排序,在阶段i
(i = 0, 1, 2, 3, ...
)中,只考虑每个后缀的前2^i
个字符。
按照第一个字符对后缀进行排序是微不足道的,对您来说应该没问题。在这个(“第0个”)阶段之后,我们将部分排序后缀数组和另一个数组,其中包含后缀分区到桶中:每个桶保留带有相同第一个符号的后缀。
现在,想象一下我们已经完成了阶段i
并立即处理阶段i + 1
。我们需要比较属于同一个存储桶的两个后缀s(t)
和s(q)
。 (让s(t)
为原始字符串中位置t
的后缀。)
由于第一个2^i
字符对于它们是相同的,我们只需考虑下一个2^i
字符(因此,总数将为2^(i+1)
)。但是没有第一个s(t)
符号的后缀2^i
是s(t + 2^i)
。因此,我们只需要根据他们的第一个s(t + 2^i)
符号对s(q + 2^i)
和2^i
进行比较,我们已经从阶段i
获得了此信息。
结束。第一次实施它可能有点棘手(也是一个很好的练习),但这是个主意。请注意,我们从原始字符串中读取实际字符的唯一时间是步骤0
。然后,在步骤i
上,我们仅使用步骤i - 1
中的结果。
修改强>
This original paper from 1989提供了更多细节。 (更多细节不是必要的,理解它和实施比需要更复杂,恕我直言。)
答案 1 :(得分:1)
朴素排序会使后缀数组构造为O(n ^ 2logn),而不是O(n ^ 2)。
但是,有一些方法可以在O(n)中构造后缀数组[一种简单的方法是构造一个后缀树并将其转换为后缀数组 - 后缀树结构为O(n)]。
我不知道是否可以用少于O(n)的空间。