【问题标题】:Find the dominant cyclic substring of a given string查找给定字符串的主要循环子字符串
【发布时间】:2013-09-13 18:27:40
【问题描述】:

我正在寻找一种算法来找到给定字符串的显性循环子字符串

一个循环子串

  • 相邻重复两次或更多次的子字符串。

一个显性循环子串

  • 重复次数最多的子串占优势
  • (相邻重复出现的次数相同)
    • 最长的子串占主导地位
  • (关于长度和相邻重复的关系)
    • 最先出现的子字符串占主导地位

示例 1:

  • prefixgarbagecyclecyclecyclecyclesufixgarbage
  • 返回cycle:=> cycle 是重复次数最多的相邻子串

示例 2:

  • prefixgarbagecyclepadinggarbagecyclesufixgarbage
  • 返回g:=> 出现的cycle 不相邻重复,g 相邻重复两次

示例 3:

  • prefixgarbagecyclecyclepadinggarbageprefixgarbage
  • 返回cycle:=> cycle & g 重复两次相邻但cycleg

示例 4:

  • prefixgarbagecyclecyclecycleroundroundroundprefixgarbage
  • 返回 cycle:=> cycle & round 相邻重复三次且长度相同,但 cycle 首先出现

示例 5:

  • abcdefghijklmnopqrstuvqxyz
  • 返回<empty string>,因为没有重复的相邻子串

实现该算法的最佳方法是什么?

【问题讨论】:

  • 为什么您的示例 1 没有 cyclecycle 作为主要子字符串?它重复了两次......
  • 因为cycle 重复了 4 次,所以重复比长度更重要
  • 您自己对如何处理这个问题有任何想法吗?您在字符串处理中使用的任何最喜欢的语言?您所说的“最佳实施方法”是什么意思?一种非常有效的方法是“尝试并修改直到它起作用”,但这可能不是您要问的。
  • @Lee Meador 返回 a :=> axyzbaaabaaabaaaaxyz 中相邻重复 4 次
  • @Floris 是一种明显适用的技术:ie 分而治之动态编程,等.

标签: string algorithm language-agnostic substring cycle


【解决方案1】:

找不到比这个二次时间算法更好的东西(用 Python 实现):

IREP, IPER, IPOS = 0, 1, 2

def find_dominant(src):
  best = (0, 0, 0) # repetitions-1, period, position

  period = 0
  while period < len(src) // max(2, 1 + best[IREP]):
    period += 1
    length = 0

    for pos in range(len(src) - 1 - period, -1, -1):
      if src[pos] == src[pos + period]:
        length += 1
        repetitions = length // period
        if repetitions >= best[IREP]:
          best = (repetitions, period, pos)
      else:
        length = 0

  return best


s = "prefixgarbagecyclecyclecyclecyclesufixgarbage"
res = find_dominant(s)
if res[0] == 0:
  print("nothing found")
else:
  print(res[IREP] + 1, '*', s[res[IPOS]: res[IPOS] + res[IPER]])

对于每个可能的周期扫描字符串并记住最长的周期子序列。向后扫描以检查较少的条件。在没有进一步改善的情况下停止增加周期。

时间复杂度为O(N2 / R),其中R是主导子串的重复次数。空间复杂度为 O(1)。

【讨论】:

  • 我喜欢提前终止标准。在这些示例中,它平均减少了大约 35% 的循环数 - 比其他的多一些。
【解决方案2】:

从左到右扫描。

您需要一些键/值对。关键是一封信。该值包括在扫描中找到到该点的字母的最后一个实例的索引以及有关该字母所属的任何循环的信息,其中包含两个或多个字符串(该列中以该字母开头的最后一个)。

您需要一个地方来存储有关找到的任何周期的信息。称之为“自行车商店”。

当您在每个索引处扫描时:

  • 使用那里的字母。看看它是否在键表中。
  • 如果找到,请向前看,看看下面的字母是否与在表中找到的字母(上一次出现)和这个字母(在当前扫描索引处)之间的字母匹配。
  • 如果它们匹配,我们就有了一个循环,看看前一次出现是否有信息表明这是存储的循环信息的延续。 (注意:相邻字母可能是特殊情况。)
  • 如果是续集,
    • 添加到存储的周期信息以包含这些字符
    • 更新最后一次找到该字母的索引
    • 在循环商店中更新有关此循环的信息
  • 如果不是延续
    • 创建(或替换)存储的周期信息以显示这个新周期 (count=2)
    • 更新最后一次找到该字母的索引
    • 在自行车商店中添加有关此自行车的信息
  • 如果出现后的字母与出现后的字母不匹配,
    • 删除此字母的所有存储周期信息
    • 更新最后一次找到该字母的索引
  • 如果该字母不在表中,请为该字母和该索引添加一个键/值对。

完成扫描后,查看自行车商店,看看哪个占主导地位。

请注意,您可能不必将所有循环存储到最后,但对于我来说,如何决定可以丢弃哪些循环并不明显。可能是关于根据键/值对表的内容以及迄今为止占主导地位的内容来保留仍然可能继续的那些。

【讨论】:

  • 你认为这比我的蛮力方法更有效吗?我发现很难估计这将花费的处理量。有什么想法吗?
  • @Floris 我怀疑是这样,但正如你所说,这很难说。我的基本想法是 O(n) 加上检查。当检查与另一个迭代不匹配时,您的基本上是 o(n*log n) 短路。两者都可能更有效。
【解决方案3】:

这是一种可能的方法(由于您的周期必须相邻,因此变得更简单)。选择一个字符串。看看会不会重复。跟踪重复次数最多的一个。

编辑实际测试的 Python 代码:

testStrings =[ "prefixgarbagecyclecyclecyclecyclesufixgarbage",
               "prefixgarbagecyclepadinggarbagecyclesufixgarbage",
               "prefixgarbagecyclecyclepadinggarbageprefixgarbage",
               "prefixgarbagecyclecyclecycleroundroundroundprefixgarbage",
               "abcdefghijklmnopqrstuvqxyz"];

for input in testStrings:

 repCountMax = 0
 longestCycle = ""
 repCount = 0
 for i in range (1, len(input)):
  for j in range( i+1, len(input)):
    substring = input[i:j]
    #print substring
    ls = len(substring)
    repCount = 1
    k = j
    while(substring == input[k:k+ls]):
      k = k + ls
      repCount = repCount +1
      #print "repetition ", repCount, " of ", substring, "\n"
    if (repCount > repCountMax) or ((repCount == repCountMax) and len(substring) > len(bestCycle)):
      repCountMax = repCount
      bestCycle = substring

 if repCountMax > 1:
  print "best cycle in '", input, "' is '", bestCycle,"' which is repeated ", repCountMax, " times."
 else:
  print "no repeated cycles found in string ", input

结果输出:

'prefixgarbagecyclecyclecyclecyclecyclesufixgarbage'中的最佳循环是' ecycl ' 重复 4 次。

'中的最佳循环 prefixgarbagecyclepadinggarbagecyclesufixgarbage ' 是 ' g ' 这是 重复 2 次。

'中的最佳循环 prefixgarbagecyclecyclepadinggarbageprefixgarbage ' 是' ecycl ' 其中 重复 2 次。

'中的最佳循环 prefixgarbagecyclecyclecyclecycleroundroundroundprefixgarbage ' is 'ecycl ' 重复3次。

在字符串中没有发现重复的循环 abcdefghijklmnopqrstuvqxyz

注意 - 找到的循环是 ecycl,而不是 cycleecycl 先发生...

第二点——当你不能再“击败”当前的最佳估计时,你可以通过停止来稍微提高效率——例如,如果你已经找到了五个重复,并且给定了你正在搜索的字符串的大小没有空间重复六次。当有大量重复时,这将提高速度。请参阅 Evgeny 的解决方案,了解实现该方法的方法。

【讨论】:

  • 不应该input(k:end) 更像input(k:k+(j-i)),最后一个else 应该是else if repCount == repCountMax &amp;&amp; strlen...
  • @LeeMeador - 同意第一点;感谢您指出。不确定是否需要第二个。事实上,我把整个else 拿出来了……查看更新的代码。
【解决方案4】:

我认为这里可以应用一种修改形式的 KMP 算法。

从头开始遍历字符串。

取出第一个字母,存储它。取下一个字母,如果相同,则有2个候选重复循环,否则将其添加到现有字符串中。

基本上,在每个字母处,您都会有一个可能的子字符串列表,用于重复的可能性。当然,您必须跟踪长度和最大编号。到那时为止的重复。

如果我没记错的话,这可以在 O (n) 时间内完成。

【讨论】:

  • 随着可能匹配的字符串长度的增长,这不会变成 O(n^2) 吗?如果不是,为什么不呢? KMP 是 O(n+k) 匹配一个字符串 - 但这里我们需要检查所有可能的字符串子集...
猜你喜欢
  • 2023-01-19
  • 1970-01-01
  • 2016-04-13
  • 2016-02-01
  • 2012-04-20
  • 2012-09-07
  • 2016-04-20
  • 1970-01-01
  • 2013-10-22
相关资源
最近更新 更多