在数字序列的末尾找到重复序列

时间:2012-05-04 02:00:09

标签: arrays algorithm language-agnostic sequence repeat

我的问题是:我有一大堆数字。我知道,在某一点之后,它变成了周期性的 - 也就是说,在序列的开头有k个数字,然后还有更多的数字在序列的其余部分重复。作为一个更清楚的例子,序列可能如下所示:[1,2,5,3,4,2,1,1,3,2,1,1,3,2,1,1,3 ,...],其中k是5,m是4,然后重复块是[2,1,1,3]。从这个例子可以清楚地看出,我可以在较大的块内部重复位,因此只查找第一个重复实例是没有用的。

但是,我不知道k或m是什么 - 我的目标是将序列[a_1,a_2,...,a_n]作为输入并输出序列[a_1,...,a_k,[ a_(k + 1),...,a_(k + m)]] - 基本上通过将大部分序列列为重复块来截断较长的序列。

有没有一种有效的方法来解决这个问题?此外,计算可能更难但更理想 - 我可以在生成相关序列时执行此操作,这样我必须生成最小量?我在这个网站上看过其他类似的问题,但它们似乎都处理序列而没有开始的非重复位,并且通常不必担心内部重复。

如果它有用/有用,我也可以了解为什么我要看这个以及我将用它做什么。

谢谢!

编辑:首先,我应该提到我不知道输入序列是否在重复块的结尾完成。

我正在努力解决的现实问题是为二次非理性(实际上是负CFE)的连续分数扩展(CFE)写一个漂亮的,封闭形式的表达式。为这些CFE生成部分商*非常简单 - 但是,在某些时候,二次无理的CFE尾部变成了重复块。我需要在这个重复块中使用部分商。

我目前的想法是这样的:也许我可以调整一些建议的工作,从右边开始使用其中一个序列。或者,也许有证据表明为什么二次非理性是周期性的,这将有助于我理解为什么它们开始重复,这将有助于我提出一些简单的标准来检查。

*如果我将连续分数扩展写为[a_0,a_1,...],我将a_i称为部分商。

有兴趣的人可以在这里找到一些背景信息:http://en.wikipedia.org/wiki/Periodic_continued_fraction

5 个答案:

答案 0 :(得分:7)

您可以使用rolling hash来实现线性时间复杂度和O(1)空间复杂度(我认为是这种情况,因为我不相信您可以拥有两个频率的无限重复序列不是彼此的倍数。)

算法:你只需保留两个滚动的哈希值,如下所示:

                       _______  _______  _______
                      /       \/       \/       \
...2038975623895769874883301010883301010883301010
                      .        .        .      ||
                      .        .        .    [][]
                      .        .        .  [ ][ ]
                      .        .        .[  ][  ]
                      .        .       [.  ][   ]
                      .        .     [  . ][    ]
                      .        .   [    .][     ]
                      .        . [      ][      ]
                      .        [       ][       ]

继续为整个序列执行此操作。第一遍只检测重复的重复2 * n次,对于某个n值。然而,这不是我们的目标:我们在第一轮中的目标是检测所有可能的时期,这样做。当我们继续执行此过程的序列时,我们还会跟踪我们稍后需要检查的所有相对黄金时段:

periods = Set(int)
periodsToFurthestReach = Map(int -> int)

for hash1,hash2 in expandedPairOfRollingHashes(sequence):
    L = hash.length
    if hash1==hash2:
        if L is not a multiple of any period:
            periods.add(L)
            periodsToFurthestReach[L] = 2*L
        else L is a multiple of some periods:
            for all periods P for which L is a multiple:
                periodsToFurthestReach[P] = 2*L

在此过程之后,我们列出了所有期间以及它们达到了多远。我们的回答可能是距离最远的那个,但我们会检查所有其他时段是否重复(快速因为我们知道我们正在检查的时段)。如果这在计算上很困难,我们可以通过修剪掉期间(停止重复)来优化,因为我们正在通过列表,非常像Eratosthenes的筛子,通过保持优先级队列,当我们接下来期望重复一段时间。

最后,我们仔细检查结果,以确保没有哈希冲突(即使有黑名单,也不会重复)。

这里我假设你的目标是尽量减少非重复长度,而不是给出一个可以进一步考虑的重复元素;您可以修改此算法以查找所有其他压缩(如果存在)。

答案 1 :(得分:2)

所以,ninjagecko为我提出的问题提供了一个很好的工作答案。非常感谢!然而,我最终找到了一种更有效的,基于数学的方法来完成我正在研究的特定情况 - 也就是说,为二次无理的连续分数扩展写出一个封闭的表达式。显然,这个解决方案只适用于这个特定情况,而不是我所询问的一般情况,但我认为将它放在这里可能是有用的,以防其他人有类似的问题。

基本上,我记得当且并且只有当它的持续分数扩展是纯粹的周期性时,二次无理性才会减少 - 例如,它从一开始就重复,没有任何主导条款。

当您计算出数字x的连续分数展开时,基本上将x_0设置为x,然后形成序列[a_0; a_1,a_2,a_3,...]通过定义a_n = floor(x_n)和x_(n + 1)= 1 /(x_n-a_n)。通常,您只需继续操作,直到达到所需的精度。然而,为了我们的目的,我们只运行这个方法,直到x_k是一个减少的二次无理(如果它大于1且它的共轭在-1和0之间,则会发生)。一旦发生这种情况,我们就知道a_k是我们重复块的第一个术语。然后,当我们发现x_(k + m + 1)等于x_k时,我们知道a_(k + m)是我们重复块中的最后一个项。

答案 2 :(得分:1)

从右侧搜索:

  • 执行a_n == a_n-1
  • 做(a_n,a_n-1)==(a_n-2,a_n-3)
  • ...

这显然是O(m ^ 2)。唯一可用的界限似乎是m< n / 2,所以它是O(n ^ 2)

这对您的申请是否可以接受? (我们是否正在为您做功课,或者这里是否存在真实的现实问题?)

答案 3 :(得分:1)

This page列出了几种良好的循环检测算法,并在C中提供了算法的实现。

答案 4 :(得分:1)

重复多次后,请考虑序列。它将结束,例如... 12341234123412341234。如果你把字符串的重复部分放到重复的最后一个循环之前,然后沿着那个循环的长度滑动它,你会发现序列末尾的子字符串和相同的子串向左滑动的距离与其长度相比较小。

相反,如果你有一个字符串,其中a [x] = a [x + k]表示大量的x,那么你也有[x] = a [x + k] = a [x + 2k ] = a [x + 3k] ...所以当滑动短距离与其长度相比时,匹配自身的字符串必须包含重复。

如果查看http://en.wikipedia.org/wiki/Suffix_array,您将看到您可以按线性时间的排序顺序构建字符串的所有后缀列表,还会显示一个数组,该数组告诉您每个后缀有多少个字符与排序顺序的前一个后缀相同。如果你查找具有最大值的条目,这将是我的候选字符串..1234123412341234,并且两个后缀的起始点之间的距离将告诉您序列重复的长度。 (但实际上某些滚动哈希搜索如http://en.wikipedia.org/wiki/Rabin-Karp可能会更快更容易,尽管有相当可编码的线性时间后缀数组算法,例如"简单线性工作后缀数组构造"由Karkkainen和桑德斯)。

假设您在可用字符数为8,16,32,64,...... 2 ^ n时应用此算法,并且最终在2 ^ p处找到重复。你在早期阶段浪费了多少时间? 2 ^(p-1)+ 2 ^(p-2)+ ...,总和约为2 ^ p,因此重复搜索只是一个恒定的开销。