String 寻找线性时间中最长的双后缀
给定一个字符串String 寻找线性时间中最长的双后缀,string,algorithm,suffix-tree,suffix,String,Algorithm,Suffix Tree,Suffix,给定一个字符串s,查找时间复杂度O(| s|)中最长的双后缀 例如:对于字符串banana,LDS是na。对于abaaba它是baa 显然,我考虑过使用后缀树,但我很难在其中找到双后缀 反转字符串并构建稀疏数组p[i][j],其中i是从0到log(n),j是从0到n-1,n是字符串的长度P[i][j]指后缀从位置j和长度2^i开始的等级。因此,如果P[i][j]=P[i][k],则索引j和k处后缀的第一个2^i字符相等 现在,您的问题归结为查找0(反向字符串的开头)的最长公共前缀和索引i处的另一
s
,查找时间复杂度O(| s|)中最长的双后缀
例如:对于字符串banana
,LDS是na
。对于abaaba
它是baa
显然,我考虑过使用后缀树,但我很难在其中找到双后缀 反转字符串并构建稀疏数组p[i][j]
,其中i
是从0
到log(n)
,j
是从0
到n-1
,n
是字符串的长度P[i][j]
指后缀从位置j
和长度2^i
开始的等级。因此,如果P[i][j]=P[i][k]
,则索引j
和k
处后缀的第一个2^i
字符相等
现在,您的问题归结为查找0
(反向字符串的开头)的最长公共前缀和索引i
处的另一个后缀,例如LCP>=i
。
其中,LCP可以通过简单地使用log(n)
时间中的P
数组来计算,首先比较这两个后缀的2^x
字符,然后逐渐减少x
总复杂度为n*log(n)*log(n)
。
这里是工作C++源代码:我认为Gene的解决方案是更简单的实现,因为它不依赖于树状结构,而是在数组上,它也可能更友好的硬件。
但是,既然您提到了后缀树,那么让我们研究一种基于后缀树的解决方案!我假设您使用一个结束标记来标记插入到树中的字符串的结束。为了说明这一点,下面是为abaa
示例构建的后缀树的表示:
$ - ##
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是双后缀的父节点,然后:
- P转换为后缀节点N,该节点仅包含字符串的结束标记(
$
)
- 让后缀为节点P所表示的子字符串,并附加一个结束标记(
baa$
)。如果我们使用后缀从P走下树,我们会在另一个后缀节点N(实际上不需要走下树)
- 节点P表示的子字符串是双后缀(在本例中为
baa
)
- 我们有等式| N'|=2.| P |+1和| N |=| P |+1
鉴于此,您只需在后缀节点上迭代并测试此条件。如果按长度递减的顺序迭代后缀,您可能会很贪婪:第一个匹配必然是最长的双后缀
请注意,在检查完长度为| S |/2的后缀后,我们可以停止搜索,并且只对奇数长度的后缀进行迭代(别忘了向字符串添加一个结束标记)
复杂性分析
构建后缀树是O(| S|)
设N'为后缀节点,N
为长度(|N'|-1)/2+1后缀的后缀节点。假设树的结构正确:
- 后缀可以按递增顺序存储在数组/向量中,因为树的创建按长度递增顺序添加后缀(至少使用Ukkonen算法)
- 因此,访问长度k的后缀是
O(1)
- 访问由树的节点表示的子字符串是
O(1)
,这尤其适用于N和N的父节点P
- 确定从P到N的转换是否只包含结束标记(
$
)是O(1)
- 检查| N'|=2.| P |+1是否确实是
O(1)
由于我们以长度递减的顺序迭代后缀,因此我们必须关注N'
后缀(在您的示例中是双后缀,即baabaa$
),因此我们必须:
- 获取N个后缀节点,使
|N'|=2.|N |-1
:O(1)
- 获取后缀节点的父节点P
N
:O(1)
- 检查从P到N的转换是否只包含结束标记
$
:O(1)
证明:(我们忽略以下证明中的结束标记)
上述3个步骤,如果导致真正的计算,则证明存在长度为2.| p |的后缀,该后缀以p表示的子字符串开始,p也是后缀。由于此子字符串是后缀,因此长度为2.| P |的后缀必然以其结尾,因此由该子字符串QED的两个匹配项组成
由于我们将对最多(|S |/2+1)/2
后缀执行此步骤,因此在最坏的情况下,标识步骤是O(| S |)
因此,总体复杂性是O(| S |)
这是你的家庭作业吗?我想你可以为字符串的反面构造一个z数组,然后扫描它,寻找最大的元素,使z[I]=I。参见例如@Amit Good point。但是仅仅寻找最大的i.s.t.z[i]>=i难道还不够吗?“额外的匹配尾”可以忽略。@XtremeJoe我提出的算法似乎在Amit启发的修改下工作得很好。为字符串的反面构造一个z数组,然后扫描它以查找最大的i,使z[i]>=i。由于z[i]是从i开始的最长子字符串的长度,该子字符串与从0开始的字符串的前缀相匹配,因此k的值,其中z[k]>=k是长度为k的重复前缀的长度。最大的k必须是答案。z-阵列可以在线性时间内构造。扫描它的最大双前缀也是O(n)。如果这不起作用,我想知道原因。@XtremeJoe是的。你应该