【问题标题】:Possible Interview Question: How to Find All Overlapping Intervals可能的面试问题:如何找到所有重叠的区间
【发布时间】:2010-12-28 00:33:04
【问题描述】:

这不是一个面试问题本身,因为我在我的项目中遇到过这个问题,但我认为这可能是一个不错的面试问题。

你有 N 对区间,比如整数。您需要识别在 O(N) 时间内相互重叠的所有间隔。例如,如果您有

{1, 3} {12, 14} {2, 4} {13, 15} {5、10}

答案是 {1, 3}, {12, 14}, {2, 4}, {13, 15}。请注意,您不需要对它们进行分组,因此结果可以按照示例中的任何顺序排列。

我只是投入了 O(N) 时间,因为 KMP 算法需要 O(N) 进行字符串搜索。 :D

我想出的最好的并且我现在在项目中使用的是 O(N^2)。是的,蛮力很可悲,但没有人抱怨,所以我不会重构它。 :P 不过,我很好奇一个更伟大的头脑是否有更优雅的解决方案。

【问题讨论】:

  • 有两件事不清楚:(1)你说“N对间隔”,虽然我很确定你实际上是指“N个间隔”,因为如果只有N pairs 所有重叠都可以在 O(N) 中轻松找到:-P 假设 N = 间隔数:(2) 不可能在 O(N) 时间内报告所有重叠对,因为可能存在 O(N ^2) 其中! OTOH 要求 O(N) 大小的 所有与至少一个其他区间重叠的区间 的集合是合理的。这是你要求的吗?
  • gbenison 的答案 (stackoverflow.com/a/9775727/47984) 是目前唯一真正回答 O(nlog n) 问题的 9 个答案。请考虑将该答案标记为正确。
  • 这很有趣,因为我接受了亚马逊的采访,他们问了我一个类似的问题....
  • @j_random_hacker:你能解释一下为什么marcog from的答案不是O(n lg n)吗?
  • @stackoverflowuser2010:问题主要是问题的表述非常糟糕,正如我在第一条评论中所写的那样。从字面上解释,它没有解决方案,所以回答者(合理地)寻找“类似”的问题。如果我们将 marcog 的主张“我们可以找到哪些区间与哪些重叠……”解释为列出所有重叠区间对,这与他后来声称“这是一个 O(N logN) 解决方案”相矛盾——可能存在 O (n^2) 对,no 算法可以在 O(n log n) 时间内列出。

标签: algorithm


【解决方案1】:

将区间的端点放入一个数组中,将它们标记为起点或终点。如果间隔是封闭的,则通过将结束点放在起点之前来打破平局对它们进行排序,如果它们是半开的,则相反。

1S, 2S, 3E, 4E, 5S, 10E, 12S, 13S, 14E, 15E

然后遍历列表,跟踪我们所处的间隔数(这等于处理的起点数减去终点数)。每当我们在一个区间内到达起点时,这意味着我们必须有重叠的区间。

1S, 2S, 3E, 4E, 5S, 10E, 12S, 13S, 14E, 15E
    ^                          ^
   overlap                    overlap

我们可以通过在端点旁边存储数据并跟踪我们所处的区间来找到哪些区间与哪些区间重叠。

这是一个 O(N logN) 的解决方案,排序是主要因素。

【讨论】:

  • “通过在起点之前放置终点来打破平局” - 取决于如何定义间隔。如果它们是半开的,那么{1,2} and {2,3} 不要重叠。如果它们是封闭的区间,那么这是一个重叠。问题没有具体说明。
  • @marcog:不确定,但算法真的是 O(nlogn) 吗?如果您需要返回哪些区间相互重叠,它似乎更像是 O(n^2)。当所有区间重叠时(如 {1,8}、{2,7}、{3,6}、{4,5})O(n^2) 区间在解中。
  • @Gruber:我认为你是对的。尽管如此,如果我们只想要 O(N_intervals) 大小的一组与其他区间重叠的区间,我们可以通过第二次重复算法来得到这个,但从最后开始向后运行,并取这个和结果的并集第一次运行。我们还必须检查顶级间隔。为什么?如果区间 X 与其他区间 Y 重叠,则以下至少一项为真: Y 的起点在 X 之前(X 在阶段 1 上捕获); Y 的结束跟随 X 的结束(X 在阶段 2 中捕获); Y 完全包含在 X 中而 X 位于顶层
  • @Gruber:请参阅 gbenison 的可爱答案以了解不同的方法。
  • @faizan 原发帖人问的问题不清楚,造成混乱。正如 j_random_hacker 在顶部的评论中所说:“不可能在 O(N) 时间内报告所有重叠对,因为它们可能有 O(N^2) 个!OTOH 要求 O(N) 是合理的) 大小的所有间隔的集合,这些间隔至少与一个其他间隔重叠”。 marcog 和 gcbenison 都回答了 O(N log N) 中的第二个问题
【解决方案2】:

按起点对区间进行排序。然后折叠这个列表,如果它们重叠,则将每个间隔与其邻居合并(即(1,4),(2,6)->(1,6))。结果列表包含那些没有重叠伙伴的间隔。从原始列表中过滤掉这些。

这是在初始排序操作之后的线性时间,可以使用任何 (n log n) 算法完成。不知道你会如何解决这个问题。如果您利用第一个操作的输入和输出已经排序的顺序,那么即使最后的“过滤掉重复项”操作也是线性的。

这是 Haskell 中的一个实现:

-- Given a list of intervals, select those which overlap with at least one other inteval in the set.
import Data.List

type Interval = (Integer, Integer)

overlap (a1,b1)(a2,b2) | b1 < a2 = False
                       | b2 < a1 = False
                       | otherwise = True
                                     
mergeIntervals (a1,b1)(a2,b2) = (min a1 a2, max b1 b2)
                                     
sortIntervals::[Interval]->[Interval]
sortIntervals = sortBy (\(a1,b1)(a2,b2)->(compare a1 a2))

sortedDifference::[Interval]->[Interval]->[Interval]
sortedDifference [] _ = []
sortedDifference x [] = x
sortedDifference (x:xs)(y:ys) | x == y = sortedDifference xs ys
                              | x < y  = x:(sortedDifference xs (y:ys))
                              | y < x  = sortedDifference (x:xs) ys

groupIntervals::[Interval]->[Interval]
groupIntervals = foldr couldCombine []
  where couldCombine next [] = [next]
        couldCombine next (x:xs) | overlap next x = (mergeIntervals x next):xs
                                 | otherwise = next:x:xs
                                               
findOverlapped::[Interval]->[Interval]
findOverlapped intervals = sortedDifference sorted (groupIntervals sorted)
  where sorted = sortIntervals intervals
                                     
sample = [(1,3),(12,14),(2,4),(13,15),(5,10)]

【讨论】:

  • 这实际上是我在这里看到的唯一答案,它确实会找到与其他间隔重叠的所有间隔,并且在 O(nlog n) 时间内完成。 (marcog 的算法是一个开始,但实际上是 O(n^2)。)我喜欢“减去”组合区间(包括所有不重叠的区间)以找到重叠区间的想法。跨度>
  • 我必须说我通常有点慢,但我认为我没有完全掌握这个解决方案。您确定此解决方案可以找到所有可能的重叠间隔对吗?我还看到,在您的解决方案的标题中,评论说:-给定一个间隔列表,选择与集合中至少一个其他间隔重叠的那些。这与提出的问题不同。我在这里错过了什么吗?
  • 我认为您的overlap 条件可以简化为overlap (_, b1) (a2,_) | b1 &gt; a2 = True | otherwise = False,或者简单地说:overlap (_, b1) (a2,_) = b1 &gt; a2 假设 a1 已排序。否则,只需overlap (_, b1) (a2,_) = (b1&gt;a2) &amp;&amp; (a1&lt;a2)
【解决方案3】:

intervales-on-the-line 问题的标准方法是根据起点对它们进行排序,然后从头到尾遍历。 O(n*logn)O(n)如果已经排序)

end = 0;
for (current in intervals) {
    if current.start < end {
        // there's an intersection!
        // 'current' intersects with some interval before it
        ...
    }
    end = max(end, current.end)
}

【讨论】:

  • 在宣布隔离之前,您仍然需要检查当前间隔是否与即将到来的间隔相交。
  • @jbx 我没有说current 间隔立即“宣布为隔离”,是吗?我什至没有说这是一个解决方案。有很多方法可以使该方法适应这个特定问题,例如isolated[current - 1] = false 或者你提到的那个。
  • 这只是解决方案的一小部分。您忘记了可以有独立的重叠间隔集。例如:{0, 5}, {1, 6}, {40, 45}, {41, 46}
  • @Nikita 所以你只是给出了部分解决方案,剩下的留给想象? :)
  • @Ilya 在伪代码注释中没有说“让我们检查语句 X”,而是说“语句 X 是真的”。
【解决方案4】:

不确定 O(N) 但如果我们首先按每个元组中的第一个数字对它们进行排序,然后依次找到元组的第一个数字大于之前元组中看到的最大数字的那些,该怎么办?它也不与下一个元组重叠。

所以你会首先得到:

{1, 3}, {2,4}, {5, 10}, {12, 14}, {13, 15}

由于 4(最大)

这需要我们跟踪我们遇到的最大数字,并且每次我们找到一个起始数字更大的元组时,我们检查它是否与下一个重叠。

这取决于排序算法的效率,因为后面的过程将是 O(N)

【讨论】:

  • 没那么简单。您的算法会说这里的最后两个间隔是“孤立的”:{1, 10} {2, 3} {4, 5} {6, 7}
  • 你是对的......我们必须跟踪最大的第二个数字。
【解决方案5】:

假设起点和终点之间的差异很小,例如 [1, 2] -> 001; [2, 3]-> 010; [1, 3] -> 011; [2, 3, 4] -> 110。如果按位 AND 非零,则两个区间或区间组合会重叠。例如。 [1,2][1,3] 重叠,因为 001&amp;011 == 001 非零。 O(n) alg 是保持到目前为止看到的间隔的按位 OR 和 AND 每个新的间隔:

bitsSoFar = 0
for (n=0; n < bitPatterns.length; n++)
    if (bitPatterns[n] & bitsSoFar != 0)
        // overlap of bitPatterns[n] with an earlier pattern
        bitsSoFar |= bitPatterns[n]

留作练习:

  • 修改算法以识别位模式与后一个位模式的重叠

  • 在 O(1) 中计算出一个区间的位模式

【讨论】:

    【解决方案6】:

    如果N对区间是整数,那么我们可以在O(n)中得到它。

    按对中的第一个数字排序,然后是第二个数字。如果都是整数,我们可以使用桶排序或基数排序来通过O(n)得到它。

    {1, 3}, {2,4}, {5, 10}, {12, 14}, {13, 15}

    然后一一组合,

    {1,3}

    {1,4} 与 {1,3} 和 {2,4} 重叠

    {1,4}、{5,10}

    {1,4}、{5,10}、{12,14}

    {1,4}、{5,10}、{12,15} 与 {12,14} 和 {13,15} 重叠

    组合需要 O(N) 时间

    【讨论】:

      【解决方案7】:

      这是 Java 中的 O(N lg N) 实现,扩展了@Nikita Rybak 提供的答案。

      我的解决方案找到与至少一个其他间隔重叠的每个间隔,并将它们都计为重叠间隔。例如,来自 OP 原始问题的两个区间 (1, 3)(2, 4) 相互重叠,因此在这种情况下有 2 个重叠区间。换句话说,如果区间 A 与区间 B 重叠,那么我将 A 和 B 添加到重叠的区间的结果集中。

      现在考虑区间(1, 100)(10, 20)(30, 50)。我的代码会发现:

      [ 10,  20] overlaps with [  1, 100]
      [ 30,  50] overlaps with [  1, 100]
      
      Resulting intervals that overlap with at least one other interval:
      [  1, 100]
      [ 30,  50]
      [ 10,  20]
      

      为了防止(1, 100)被计算两次,我使用了一个JavaSet,它只保留唯一的Interval对象。

      我的解决方案遵循这个大纲。

      1. 按起点对所有区间进行排序。这一步是O(N lg N)
      2. 跟踪intervalWithLatestEnd,与最近结束点的间隔。
      3. 遍历排序列表中的所有区间。如果间隔与intervalWithLatestEnd 重叠,则将两者都添加到集合中。需要时更新intervalWithLatestEnd。这一步是O(N)
      4. 返回 Set(并在需要时转换为 List)。

      总运行时间为O(N lg N)。它需要一个大小为O(N)的输出集。

      实施

      为了将间隔添加到集合中,我创建了一个自定义 Interval 类,如预期的那样覆盖 equals()

      class Interval {
          int start;
          int end;
          Interval(int s, int e) { 
              start = s; end = e; 
          }
      
          @Override
          public String toString() {
              return String.format("[%3d, %3d]", start, end);
          }
          @Override
          public int hashCode() {
              final int prime = 31;
              int result = 1;
              result = prime * result + start;
              result = prime * result + end;
              return result;
          }
          @Override
          public boolean equals(Object obj) {
              if (this == obj)
                  return true;
              if (obj == null)
                  return false;
              if (getClass() != obj.getClass())
                  return false;
              final Interval other = (Interval) obj;
              if (start != other.start)
                  return false;
              if (end != other.end)
                  return false;
              return true;
          }
      }
      

      下面是运行算法的代码:

      private static List<Interval> findIntervalsThatOverlap(List<Interval> intervals) {
      
          // Keeps unique intervals.
          Set<Interval> set = new HashSet<Interval>();
      
          // Sort the intervals by starting time.
          Collections.sort(intervals, (x, y) -> Integer.compare(x.start, y.start));
      
          // Keep track of the interval that has the latest end time.
          Interval intervalWithLatestEnd = null;
      
          for (Interval interval : intervals) {
      
              if (intervalWithLatestEnd != null &&
                  interval.start < intervalWithLatestEnd.end) {
      
                  // Overlap occurred.
                  // Add the current interval and the interval it overlapped with.
                  set.add(interval); 
                  set.add(intervalWithLatestEnd);
      
                  System.out.println(interval + " overlaps with " +
                                     intervalWithLatestEnd);
              }
      
              // Update the interval with latest end.
              if (intervalWithLatestEnd == null ||
                  intervalWithLatestEnd.end < interval.end) {
      
                  intervalWithLatestEnd = interval;
              }
          }
          // Convert the Set to a List.
          return new ArrayList<Interval>(set);
      }
      

      测试用例

      这是一个运行 OP 原始区间的测试用例:

      public static void testcase() {
      
          List<Interval> intervals = null;
          List<Interval> result = null;
      
          intervals = new ArrayList<Interval>();
      
          intervals.add(new Interval(1, 3));
          intervals.add(new Interval(12, 14));
          intervals.add(new Interval(2, 4));
          intervals.add(new Interval(13, 15));
          intervals.add(new Interval(5, 10));
      
      
          result = findIntervalsThatOverlap(intervals);
          System.out.println("Intervals that overlap with at least one other interval:");
          for (Interval interval : result) {
              System.out.println(interval);
          }
      }
      

      结果:

      [  2,   4] overlaps with [  1,   3]
      [ 13,  15] overlaps with [ 12,  14]
      Intervals that overlap with at least one other interval:
      [  2,   4]
      [  1,   3]
      [ 13,  15]
      [ 12,  14]
      

      最后,这是一个更高级的测试用例:

      public static void testcase() {
      
          List<Interval> intervals = null;
          List<Interval> result = null;
      
          intervals = new ArrayList<Interval>();
      
          intervals.add(new Interval(1, 4));
          intervals.add(new Interval(2, 3));
          intervals.add(new Interval(5, 7));
          intervals.add(new Interval(10, 20));
          intervals.add(new Interval(15, 22));
          intervals.add(new Interval(9, 11));
          intervals.add(new Interval(8, 25));
          intervals.add(new Interval(50, 100));
          intervals.add(new Interval(60, 70));
          intervals.add(new Interval(80, 90));
      
      
          result = findIntervalsThatOverlap(intervals);
          System.out.println("Intervals that overlap with at least one other interval:");
          for (Interval interval : result) {
              System.out.println(interval);
          }
      }
      

      结果:

      [  2,   3] overlaps with [  1,   4]
      [  9,  11] overlaps with [  8,  25]
      [ 10,  20] overlaps with [  8,  25]
      [ 15,  22] overlaps with [  8,  25]
      [ 60,  70] overlaps with [ 50, 100]
      [ 80,  90] overlaps with [ 50, 100]
      Intervals that overlap with at least one other interval:
      [  2,   3]
      [  8,  25]
      [  9,  11]
      [ 50, 100]
      [  1,   4]
      [ 15,  22]
      [ 10,  20]
      [ 60,  70]
      [ 80,  90]
      

      【讨论】:

        【解决方案8】:

        自从我使用它已经有一段时间了,但我使用的解决方案是算法简介中描述的红黑树的派生,称为区间树。它是按间隔开始排序的树,因此您可以快速(二分查找)首先找到第一个符合条件的节点。 IIRC,节点是由一个属性排序的,当候选节点与您的间隔不匹配时,您可以停止“遍历”树。所以我认为是 O(m) 搜索,其中 m 是匹配区间的数量。

        我搜索找到this implementation

        布雷特

        [编辑] 重读这个问题,这不是你问的。我认为这是最好的实现,当您有一个(例如)已经安排在会议室(已添加到树中)的会议列表,并且您希望找到哪些房间仍可用于具有新开始和持续时间的会议(搜索词)。不过,希望这个解决方案有一些相关性。

        【讨论】:

          【解决方案9】:

          这个问题可以归结为元素唯一性问题。

          元素唯一性具有 Omega(n log n) 下限(计算比较次数),所以你不能做得比这更好。

          【讨论】:

          • 在提供答案时,您应该清楚具体。我不确定你删除的帖子是什么样的,但在这篇文章中,你至少可以告诉人们如何将元素唯一性降低为区间重叠。
          【解决方案10】:

          您可以遍历列表一次,并保留到目前为止遇到的所有间隔的哈希表。如果输入区间是哈希表中某个区间的一部分,则将其合并到哈希表区间中。标记非原始区间(由多个区间组成的合并区间)。

          然后你再次检查列表,并为每个间隔检查哈希表是否包含在合并的间隔中。

          不知道是不是O(N),但是比O(N^2)好很多。

          【讨论】:

          • 唯一的问题是,hashtable不支持'区间交集'操作:)
          • 你指的是哈希表的一些具体实现吗?因为我在谈论这个概念。您可以随时自行执行操作。
          • @IlyaKogan 我认为 Nikita 试图说明的一点是,没有明显的方法可以快速找到与给定查询间隔相交的间隔。天真的方法每次查询需要 O(n) 时间,这将是一个 O(n^2) 算法。您可以使用区间树,但这与哈希表完全无关。
          • @JohnKurlak 同意。看来我错过了这部分。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-06-20
          • 2022-01-26
          • 1970-01-01
          • 2020-09-10
          • 2023-03-04
          相关资源
          最近更新 更多