【问题标题】:Checking if two substring overlaps in O(n) time检查两个子字符串是否在 O(n) 时间内重叠
【发布时间】:2015-08-02 18:06:32
【问题描述】:

如果我有一个长度为 n 的字符串 S 和一个元组列表 (a,b),其中 a 指定 S 的子字符串的起始位置,b 是子字符串的长度。要检查是否有任何子字符串重叠,例如,我们可以在 S 中标记被触摸的位置。但是,我认为如果元组列表的大小为 n(循环元组列表,然后循环 S),这将花费 O(n^2) 时间。

是否有可能在 O(n) 时间内检查任何子字符串是否实际上与另一个子字符串重叠?

编辑: 例如,S = "abcde"。元组 = [(1,2),(3,3),(4,2)],分别代表“ab”、“cde”和“de”。我想知道在读取 (4,2) 时发现了重叠。

我认为它是 O(n^2),因为你每次都会得到一个元组,那么你需要遍历 S 中的子字符串来查看是否有任何字符被标记为脏。

编辑 2: 一旦检测到碰撞,我就无法退出。想象一下,我需要报告所有发生冲突的后续元组,所以我必须遍历整个元组列表。

编辑 3: 算法的高级视图:

 for each tuple (a,b)
   for (int i=a; i <= a+b; i++)
      if S[i] is dirty 
        then report tuple and break //break inner loop only

【问题讨论】:

  • 我不明白这个问题。时间复杂度如何取决于String 的长度而不取决于元组列表的长度?
  • @pbabcdefp 我已经编辑了问题并添加了为什么我认为它是 O(n^2)
  • 重叠是什么意思?重叠与元组有关,还是与S有关?你能举出更多的例子吗?
  • @coderz 重叠意味着如果 S 的两个子字符串共享至少 1 个位置。 (3,3) 与 (4,2) 重叠,因为它们“de”(S[4] 和 S[5],S 以 1 开头)。
  • 是否需要报告每次碰撞(例如,(0, 2)(1, 1) 碰撞,(1, 1)(1, 2) 碰撞,以及 (0, 2)(1, 2) 碰撞),或者做你只需要报告每个有冲突的元组?如果您需要报告每一次碰撞,则可能不止O(n),而且仅输出所有内容可能需要比O(n)更长的时间。

标签: arrays string algorithm


【解决方案1】:

您的基本方法是正确的,但您可以优化停止条件,以确保在最坏情况下的复杂性有限。这样想——在最坏的情况下,你必须遍历和标记 S 中的多少个位置?

如果没有冲突,那么在最坏的情况下,您将访问 length(S) 个位置(并且到那时会用完元组,因为任何额外的元组都必须发生冲突)。如果发生碰撞 - 您可以停在第一个标记的对象处,因此您再次受到未标记元素的最大数量的限制,即长度(S)

编辑:由于您添加了报告所有冲突元组的要求,让我们再次计算一下(扩展我的评论) -

一旦你标记了所有元素,你可以通过一个步骤 (O(1)) 检测每个进一步的元组的冲突,因此你需要 O(n+n) = O(n)。 这一次,每个步骤要么标记一个未标记的元素(在最坏的情况下为整体 n),要么识别一个碰撞元组(最坏的 O(tuples),我们假设也是 n)。

实际的步骤可能是交错的,因为元组可以以任何方式组织而不会首先发生碰撞,但是一旦它们这样做(在第一次碰撞之前最多覆盖所有 n 个元素的 n 个元组之后),你必须发生碰撞每次都在第一步。甚至在标记所有元素之前,其他安排可能会更早发生冲突,但同样 - 您只是重新安排相同数量的步骤。

最坏的例子:一个元组覆盖整个数组,然后是 n-1 个元组(不管哪个)- [(1,n), (n,1), (n-1,1), ...(1,1)]

第一个元组需要 n 步来标记所有元素,其余的每个都需要 O(1) 才能完成。总体 O(2n)=O(n)。现在说服自己,以下示例采用相同数量的步骤 -

[(1,n/2-1), (1,1), (2,1), (3,1), (n/2,n/2), (4,1), (5 ,1) ...(n,1)]

【讨论】:

  • 感谢您的回答。你的第一个问题是,不应该是 S 的长度吗?例如,第一个元组覆盖整个 S,那么后面的所有将发生碰撞。所以第一个外循环运行 n 次,然后以下运行 O(1) 次......这是正确的吗?
  • @xcoder,如果外循环遍历元组,那么它将运行一次(在第一个元组上),让内循环将所有 n 个元素标记为已触摸,然后立即退出第一个检查第二个元组,因为它会发生冲突。
  • 也许我应该更清楚一点。一旦检测到碰撞,我就无法退出。想象一下,我需要报告所有发生冲突的后续元组,所以我必须遍历整个元组列表。
  • 这是添加一个新的约束,但不会破坏复杂性。标记所有元素后,您可以通过单次检查 (O(1)) 检测每个进一步元组的冲突,因此您需要 O(n+n) = O(n) - 每个检查都会标记一个未标记的元素(最坏情况下的整体 n),或识别一个碰撞元组(最坏的 O(tuples) 我们假设也是 n)。
  • 哦!它开始有意义了!这不是 O(n^2) 因为对于检查,我不需要每次都检查整个列表!我只是感兴趣,如果说元组列表像 [(n,1),(n-1,1),...(1,1)] 会改变什么吗?
【解决方案2】:

根据你的描述和评论,重叠问题可能与字符串算法无关,可视为“段重叠”问题。

只要用你的例子,它就可以翻译成3段:[1, 2], [3, 5], [4, 5]。问题是检查这3个段是否有重叠。

假设我们有m 段,每个段都有格式 [start, end] 表示段的开始位置和结束位置,检测重叠的一种有效算法是按开始位置升序对它们进行排序,它需要O(m * lgm)。然后对排序后的m段进行迭代,对于每个段,尝试查找其是否结束位置,这里只需要检查:

if(start[i] <= max(end[j], 1 <= j <= i-1) {
    segment i is overlap;
}
maxEnd[i] = max(maxEnd[i-1], end[i]); // update max end position of 1 to i

每个检查操作需要O(1)。那么总的时间复杂度为O(m*lgm + m),可视为O(m*lgm)。而对于每个输出,时间复杂度与每个元组的长度有关,这也与n有关。

【讨论】:

    【解决方案3】:

    这是一个段重叠问题,如果元组列表已按第一个字段的升序排序,则O(n) 本身的解决方案应该是可能的。考虑以下方法:

    1. 将间隔从 (start, number of characters) 转换为 (start, inclusive_end)。因此上面的例子变成了:[(1,2),(3,3),(4,2)] ==&gt; [(1, 2), (3, 5), (4, 5)]

    2. 如果转换后的连续元组(a, b)(c, d) 始终跟随b &lt; c,则元组有效。否则上面提到的元组有重叠。

    12 中的每一个都可以在O(n) 中完成,如果数组按照上面提到的形式排序。

    【讨论】:

      猜你喜欢
      • 2020-05-10
      • 2017-02-09
      • 2011-10-02
      • 1970-01-01
      • 2016-02-16
      • 1970-01-01
      • 1970-01-01
      • 2021-11-02
      • 1970-01-01
      相关资源
      最近更新 更多