给定两个排序的间隔列表,返回两个列表之间的重叠间隔

时间:2018-03-31 00:36:10

标签: algorithm intervals overlap interval-tree

您会收到两个时间间隔列表AB

A中,间隔按起点排序。 A内的所有区间都不重叠。

同样,在B中,间隔按起点排序。 B内的所有区间都不重叠。

返回两个列表之间重叠的间隔。

示例:

A: {[0,4], [7,12]}
B: {[1,3], [5,8], [9,11]}

返回:

{[1,3], [7,8], [9,11]} 

我在接受采访时得到了这个并且很难​​过。

我想过比较两个列表之间的间隔。如果两者之间存在重叠,请将重叠添加到结果列表中。然后我以较小的起始间隔推进列表的指针,但在面试结束时无法找到有效的解决方案。

解决此问题的最佳方法是什么?

3 个答案:

答案 0 :(得分:2)

所以你有两个包含事件的列表 - 输入间隔和离开间隔。合并这些列表,将当前状态保持为整数0,1,2(活动间隔计数)

Get the next coordinate from both lists 
If it is entering event
   Increment state
   If state becomes 2, start new output interval 
If it is closing event
   Decrement state
   If state becomes 1, close current output interval 

处理与所选规则相对应的关系(当值相等[0..1]和[1..2]时) - 如果此类间隔不应给出交叉点,则在打开关闭事件之前处理关闭事件

答案 1 :(得分:1)

这是一个实现,遵循罗马原则divide-et-impera:

首先,找一个方法,对于给定的一对间隔,找到重叠(如果有的话)。

/* Cases: A behind B, A overlap at start, A part of B, B part of A,
   overlap at end, B starts after A ended:
A:    2..3..4..5
Bs:   |        | 
0..1  |        |
0..1..2..3     |
0..1..2..3..4..5..6
      |  3..4  |
      |     4..5..6
      |        |  6..7
*/
case class Interval (lower: Int, upper: Int) {
    def overlap (other: Interval) : Option [Interval] = {
        if (lower > other.upper || upper < other.lower) None else 
        Some (Interval (Math.max (lower, other.lower), Math.min (upper, other.upper)))
    }
}

这是一种责任有限的方法,可以决定两个间隔。

如果您不熟悉Scala:Interval是一个类,第一行可以作为构造函数读取。下部和上部应该是自我解释(Int类型)。 该类有一个方法重叠,它接受类(其他)的第二个实例,并返回一个新的重叠间隔。但是包装成Option,这意味着:如果没有找到重叠,我们返回None。如果我们找到一个,那就是Some(Interval)。您可以帮助自己将此构造理解为List,它可以为空,也可以只包含一个元素。它是一种避免NullpointerException的技术,通过发信号通知可能没有结果,使用Type。

如果一个Interval的上部低于另一个Interval的下部,则不能重叠,所以我们返回None。

对于重叠,我们将两个下限的最大值作为下限,并将两个上限的最小值作为新的上限。

数据:

val a = List (Interval (0, 4), Interval (7, 12))
val b = List (Interval (1, 3), Interval (5, 8), Interval (9, 11))

天真的方法:气泡重叠(首先让它工作,然后让它快速):

scala> a.map (aa => b.map (bb => aa.overlap (bb))).flatten.flatten
res21: List[Interval] = List(Interval(1,3), Interval(7,8), Interval(9,11))

核心,如果您不习惯选择/可能与某些(T)和无,这可能有助于理解,

a.map (aa => b.map (bb => aa.overlap (bb))) 
res19: List[List[Option[Interval]]] = List(List(Some(Interval(1,3)), None, None), List(None, Some(Interval(7,8)), Some(Interval(9,11))))

第一个展平将两个内部列表合并为一个列表,第二个展平消除了Nones并留下了Intervals,而不是包装器Some(Interval)。

也许我可以提出一个迭代解决方案,它的间隔时间不会超过匹配的2倍。 ......(10分钟后)...... 这是:

def findOverlaps (l1: List[Interval], l2: List[Interval]): List[Option[Interval]] = (l1, l2) match {
    case (_, Nil) => Nil 
    case (Nil, _) => Nil
    case (a :: as, b :: bs) => {
             if (a.lower > b.upper) findOverlaps (l1, bs) 
        else if (a.upper < b.lower) findOverlaps (as, l2) 
        else if (a.upper > b.upper) a.overlap (b) :: findOverlaps (l1, bs) 
        else                        a.overlap (b) :: findOverlaps (as, l2) 
    }
}

前两行内行检查,如果列表中的任何一个为空 - 则不再需要重叠。

(a :: as,b :: bs)是(l1,l2)的匹配a是l1的头部,作为l1的尾部(可能是Nil)而模拟b是l2的头部,bs它的尾巴。

如果a.lower是&gt; b.upper,我们取b列表的尾部并用整个l1-list和类似的递归重复,整个l2-List但只有l1列表的尾部,如果b.lower&gt; a.upper。

否则我们应该有一个重叠,所以我们在任何一种情况下都采用a.overlap(b),其中一个具有较高上限的一个列表和另一个列表的尾部。

scala> findOverlaps (a, b)
res0: List[Option[Interval]] = List(Some(Interval(1,3)), Some(Interval(7,8)), Some(Interval(9,11)))

你看,没有生成无,而且对于findOverlaps(b,a)也有相同的结果。

答案 2 :(得分:1)

这是我在apache-spark程序中用作复杂reduce步骤的组件的算法实现:link to another related answer。奇怪的是,它也在斯卡拉。

这是孤立的算法:

  type Gap = (Int, Int)
/** The `merge`-step of a variant of merge-sort
  * that works directly on compressed sequences of integers,
  * where instead of individual integers, the sequence is 
  * represented by sorted, non-overlapping ranges of integers.
  */
def mergeIntervals(as: List[Gap], bs: List[Gap]): List[Gap] = {
  require(!as.isEmpty, "as must be non-empty")
  require(!bs.isEmpty, "bs must be non-empty")
  // assuming that `as` and `bs` both are either lists with a single
  // interval, or sorted lists that arise as output of
  // this method, recursively merges them into a single list of
  // gaps, merging all overlapping gaps.
  @annotation.tailrec
  def mergeRec(
    gaps: List[Gap],
    gapStart: Int,
    gapEndAccum: Int,
    as: List[Gap],
    bs: List[Gap]
  ): List[Gap] = {
    as match {
      case Nil => {
        bs match {
          case Nil => (gapStart, gapEndAccum) :: gaps
          case notEmpty => mergeRec(gaps, gapStart, gapEndAccum, bs, Nil)
        }
      }
      case (a0, a1) :: at => {
        if (a0 <= gapEndAccum) {
          mergeRec(gaps, gapStart, gapEndAccum max a1, at, bs)
        } else {
          bs match {
            case Nil => mergeRec((gapStart, gapEndAccum) :: gaps, a0, gapEndAccum max a1, at, bs)
            case (b0, b1) :: bt => if (b0 <= gapEndAccum) {
              mergeRec(gaps, gapStart, gapEndAccum max b1, as, bt)
            } else {
              if (a0 < b0) {
                mergeRec((gapStart, gapEndAccum) :: gaps, a0, a1, at, bs)
              } else {
                mergeRec((gapStart, gapEndAccum) :: gaps, b0, b1, as, bt)
              }
            }
          }
        }
      }
    }
  }
  val (a0, a1) :: at = as
  val (b0, b1) :: bt = bs

  val reverseRes = 
    if (a0 < b0) 
      mergeRec(Nil, a0, a1, at, bs)
    else
      mergeRec(Nil, b0, b1, as, bt)

  reverseRes.reverse
}

它的工作方式非常类似于合并排序的merge步骤,但您不必查看单个数字,而是必须查看整个时间间隔。原则保持不变,只有案例区别变得非常讨厌。

编辑:情况并非如此。你想要交集,这里的算法产生联合。你要么必须翻转一些if - else - 条件和min - max - 函数,要么你必须使用de-Morgan进行预处理/后处理法律。原则仍然是一样的,但我绝对不想为交叉点重复这整个练习。认为它不是一个缺点,但作为答案的一个特征:没有剧透;)