给定两个已排序的区间列表,返回这两个列表之间的重叠区间。

3
你将会得到两个区间列表,AB
A中,这些区间按照它们的起始点排序。 A内的所有区间都不重叠。
同样,在B中,这些区间按照它们的起始点排序。 B内的所有区间都不重叠。
返回两个列表之间重叠的区间。
示例:
A: {[0,4], [7,12]}
B: {[1,3], [5,8], [9,11]}

返回:

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

我在一次面试中遇到了这个问题,感到困惑。

我考虑比较两个列表之间的区间。如果两者之间有重叠部分,则将重叠部分添加到结果列表中。然后我推进具有较小起始间隔的列表的指针,但是直到面试结束都无法得出可行的解决方案。

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


是的,它们不应该重叠。使用不同的示例进行修复。 - user9577088
我已删除第二个示例,因为它没有意义。 - user9577088
3个回答

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

这里有一个实现,遵循罗马的分治原则:

首先,找到一种方法,对于给定的两个区间,找到它们的重叠部分(如果有的话)。

/* 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是一个类,第一行可以被视为构造函数。Lower和upper应该是自解释的(类型为Int)。 该类具有重叠方法,它接受该类的第二个实例(other),并将重叠的结果作为新的Interval返回。但是包装成了Option,这意味着:如果没有重叠,则返回None。如果我们找到一个,它就是Some(Interval)。您可以将其理解为List,它可以为空,也可以只包含一个元素。这是一种避免NullpointerException的技术,通过使用类型来表示可能没有结果。
如果一个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))

如果你不习惯使用Option/Maybe,其中Some(T)和None可能有所帮助,核心内容是:
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))))

第一个flatten将两个内部列表合并为一个列表,第二个flatten移除了Nones,并且让我们得到了区间,而不是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的头部,as是l1的尾部(可能是Nil),类似地,b是l2的头部,bs是l2的尾部。

如果a.lower > b.upper,则我们取b列表的尾部,并递归地使用整个l1列表和类似的方法处理整个l2列表,但只使用l1列表的尾部,如果b.lower > 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)))

您看,没有生成 None,对于 findOverlaps(b, a) 也是相同的结果。

我不熟悉Scala,所以不能很好地理解代码。您能解释一下吗?谢谢! - user9577088
它适用于这个例子吗:A: {[7,12], [8,15], [9,16]}. B: {[5,8], [9,11], [10,15}, [11,17]}。返回:{[7,16]} - user9577088
@user9577088:这违反了你所给出的断言。让我引用一下:“A 中没有任何区间重叠。”B 也是如此。 - user unknown
固定与另一个例子。 A:{[7,12],[13,19]}。 B:{[5,8],[9,11],[12,15},[16,18]}。 返回:{[7,18]} - user9577088
不行。你的第一个例子包含了(5,8)(9,11)和(7,12)。你的解决方案说(7,8)(9,11),而不是(7,11)。在有效答案出现后,你不能改变目标。你可以提出一个新问题,它不会自相矛盾,但你可以在这里邀请并链接到这个问题。由于你到目前为止没有尊重任何答案,所以我现在非常保留地投资我的时间。 - user unknown
你说得对。我还在努力理解问题和边界情况,所以提出了一个无效的例子。我已经把它删除了。由于你提供的帮助最多,我接受了你的答案。谢谢! - user9577088

1
这里是我在apache-spark程序中作为复杂减少步骤的组件使用的算法实现: 链接到另一个相关答案。有趣的是,它也是用Scala编写的。
以下是该算法的独立实现:
  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
}

它的工作方式与归并排序的合并步骤非常相似,但不是查看单个数字,而是要查看整个区间。原则保持不变,只是情况分类变得非常棘手。
编辑:不完全如此。您想要交集,而此处的算法生成并集。您需要翻转很多if-else条件和min-max函数,或者使用德摩根定律进行预处理/后处理。原理仍然相同,但我绝对不想为交集重复整个练习。不要将其视为缺陷,而是将其视为答案的特点:没有剧透;)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接