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是的。你应该