给定一个字符串s
,找出时间复杂度最长的双后缀O(|s|)
。
示例:对于 string banana
,LDS 是na
。因为abaabaa
它baa
。
显然我考虑过使用后缀树,但我很难在其中找到双后缀。
给定一个字符串s
,找出时间复杂度最长的双后缀O(|s|)
。
示例:对于 string banana
,LDS 是na
。因为abaabaa
它baa
。
显然我考虑过使用后缀树,但我很难在其中找到双后缀。
我认为 Gene 的解决方案更易于实现,并且由于它不依赖于树状结构而是依赖于阵列,因此它也可能对硬件更友好。
但是既然你提到了后缀树,那么让我们来看看基于后缀树的解决方案吧!我将假设您使用结束标记来标记您在树中插入的字符串的结尾。为了说明这一点,下面是为您的abaabaa
示例构建的后缀树的表示:
$ - ##
b a a - $ - ## // Longest double suffix: P is the first dash, N the second
b a a $ - ## // N' is the dash
a - $ - ##
a - $ - ##
b a a $ - ##
b a a - $ - ##
b a a $ - ##
当N是后缀树中的一个节点时,我们将表示|N| N表示的子串的长度。
您如何表征后缀树中的“双后缀”?嗯,它是一个终端节点N,其父节点具有特定属性:设P为双后缀的父节点,则:
$
baa$
在您的示例中)。如果我们使用suffix从P沿着树向下走,我们最终会到达另一个后缀节点N'(实际上不需要沿着树向下走)baa
在我们的例子中)。鉴于此,您只需遍历后缀节点并测试此条件。如果您按长度递减的顺序迭代后缀,您可能会变得贪婪:第一个匹配项必然是最长的双后缀。
请注意,我们可以在检查了长度为 |S|/2 的后缀后停止搜索,并且只遍历奇数长度的后缀(不要忘记我们在字符串中添加了一个结束标记)
构建后缀树是O(|S|)
.
令N'为后缀节点,并N
为长度(|N'|-1)/2 + 1的后缀的后缀节点。假设树的构造正确:
O(1)
O(1)
特别是,这适用于N和N '$
) 是O(1)
O(1)
由于我们以长度递减的顺序遍历后缀,因此我们必须关注N'
后缀(双后缀,即baabaa$
在您的示例中),因此我们只需要:
|N'| = 2.|N| - 1
:O(1)
N
:O(1)
$
:O(1)
证明:( 我们在下面的证明中忽略了结束标记)
上面的 3 个步骤,如果导致一个真实的评估,证明存在一个长度为2 的后缀。|P| 以P表示的子字符串开头,它也是一个后缀。由于这个子串是后缀,所以后缀长度为2.|P| 必然以它结尾,因此由该子串 QED 的两次出现组成。
由于我们最多对(|S|/2 + 1)/2
后缀执行此步骤,因此识别步骤是O(|S|)
最坏的情况。
因此,整体复杂度为O(|S|)
。
反转字符串并构建稀疏数组P[i][j]
,其中i
是 from 0
to log(n)
,j
是 from 0
to n-1
,n
是字符串的长度。P[i][j]
指后缀从 positionj
和 length开始的排名2^i
。因此,如果P[i][j]=P[i][k]
,索引处的后缀的第一个2^i
字符j
和k
相等。
0
现在您的问题减少到为(反转字符串的开头)和另一个后缀找到最长的公共前缀index i
,例如LCP >= i
. 其中 LCP 可以通过简单地使用时间P
数组来计算log(n)
,通过比较2^x
这两个后缀的第一个字符并逐渐减少x
.
总复杂度为n*log(n)*log(n)
。这是工作 C++ 源代码:https ://ideone.com/aJCAYG