可能的面试问题:如何找到所有重叠的间隔


72

正如我在项目中遇到的那样,这本身不是一个采访问题,但我认为这可能是一个不错的干预问题。

您有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不过,我很好奇,如果一个更大的头脑有一个更优雅的解决方案。


6
有两点不清楚:(1)您说“ N对间隔”,尽管我很确定您实际上是指“ N个间隔”,因为如果只有N对,则所有重叠都可以在O(N)中轻易找到。 :-P假设N =间隔数:(2)由于O(N ^ 2)个重叠对,因此不可能在O(N)个时间报告所有重叠对!OTOH要求所有间隔至少为一个其他间隔的O(N)大小集是合理的。这就是您要的吗?
j_random_hacker 2013年

3
gbenison的答案(stackoverflow.com/a/9775727/47984)是当前在此处实际回答O(nlog n)中问题的9个中的唯一一个。请考虑将答案标记为正确。
j_random_hacker 2013年

2
这很有趣,因为我接受了亚马逊的采访,他们问了我一个类似的问题....
AJ Meyghani 2014年

@j_random_hacker:您能解释一下为什么marcog from的答案不是O(n lg n)吗?
stackoverflowuser2010

1
@ stackoverflowuser2010:问题主要是因为我在第一个评论中写道,这个问题的措辞非常糟糕。从字面上解释,它没有解决方案,因此答复者(合理地)寻找了可以解决的“类似”问题。如果我们将marcog的主张“我们可以找到哪些区间与...重叠”解释为列出所有重叠的区间对,则这与他后来的主张“这是O(N logN)解”矛盾–可能存在O (n ^ 2)对,没有算法可以在O(n log n)时间内列出。
j_random_hacker

Answers:


98

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

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)解决方案,其中排序是主要因素。


6
“通过将端点放在起点之前来打破联系”-取决于间隔的定义方式。如果它们是半开放式的,则{1,2} and {2,3}不要重叠。如果它们是封闭的时间间隔,那么这就是一个重叠。问题没有具体说明。
史蒂夫·杰索普

5
@marcog:对此不确定,但是算法真的是O(nlogn)吗?如果需要返回彼此重叠的间隔,则看起来更像O(n ^ 2)。当所有间隔重叠时(例如在{1,8},{2,7},{3,6},{4,5}中),O(n ^ 2)个间隔在解中。
Gruber 2013年

@格鲁伯:我认为你是对的。不过,如果我们只希望O(N_intervals)大小的时间间隔集与其他时间间隔重叠,则可以通过第二次重复该算法,但从末尾开始倒转,并取其与结果的并集来获得此结果。第一次运行。我们还必须在进行操作时检查顶级间隔。为什么?如果间隔X与其他间隔Y重叠,则至少满足以下条件之一:Y的起点在X之前(X捕获在阶段1上);Y的结尾跟随X的结尾(X捕获在阶段2中);Y完全包含在X中,而X在顶层
j_random_hacker 2013年

@Gruber:有关其他方法,请参见gbenison的可爱答案。
j_random_hacker 2013年

1
@faizan原始张贴者提出的问题不清楚,这引起了混乱。正如j_random_hacker在顶部的评论中所说:“不可能报告O(N)时间内的所有重叠对,因为它们中可能有O(N ^ 2)个! )大小的所有间隔的集合,这些间隔至少与另一个间隔重叠。”。marcog和gcbenison都回答了O(N log N)中的第二个问题
antoine

34

按起点对时间间隔进行排序。然后将其折叠,将每个区间与其相邻的区间合并(即(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)。)我喜欢“减去”组合间隔(包括所有不重叠的间隔)以找到重叠间隔的想法。
j_random_hacker 2013年

1
我必须说我通常有点慢,但是我认为我没有完全掌握这种解决方案。您确定此解决方案可以找到所有可能的重叠间隔对吗?我还可以在解决方案的标题中看到以下注释:-给定间隔列表,选择与集合中至少一个其他间隔重叠的间隔。这与所问的问题不同。我在这里想念什么吗?
Pa_

我认为您的overlap条件可以简化为overlap (_, b1) (a2,_) | b1 > a2 = True | otherwise = False,或简单地:overlap (_, b1) (a2,_) = b1 > a2假设a1已排序。Otherwsie,刚刚overlap (_, b1) (a2,_) = (b1>a2) && (a1<a2)
ssm

9

在线间隔问题的标准方法是根据起点对它们进行排序,然后从头到尾走动。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

@jbx我没有current立即说间隔是“宣布隔离”,不是吗?我什至没有说这是一个解决方案。有很多方法可以使方法适应此特定问题,例如isolated[current - 1] = false您提到的方法。
Nikita Rybak 2010年

这只是解决方案的一小部分。您忘记了可以有独立的重叠间隔集。例如:{0,5},{1,6},{40,45},{
41,46

1
@Nikita,所以您只是提供了部分解决方案,其余的留给了想象力?:)
jbx

1
@Ilya在伪代码注释中没有说“让我们检查语句X”,而是说“语句X是真实的”。
Nikita Rybak 2010年

5

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

因此,您首先会得到:

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

因为4(最大)<5和10 <12,{5,10}是隔离的。

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

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


没那么简单。您的算法将说这里的最后两个间隔是“隔离的”:{1,10} {2,3} {4,5} {6,7}
Nikita Rybak 2010年

您是对的...我们必须跟踪最大的第二个数字。
jbx

2

假设起点和终点之间的差异很小,例如<32。1..32。然后,可以将每个间隔写为32位字中的位模式。例如[1, 2] -> 001; [2, 3]-> 010; [1, 3] -> 011; [2, 3, 4] -> 110。如果两个间隔或间隔的组合按位AND不为零,则它们重叠。例如。[1,2]重叠,[1,3]因为001&011 == 001,非零。O(n)alg是保持到目前为止看到的间隔和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)中计算一个间隔的位模式


2

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

我的解决方案找到了至少与另一个间隔重叠的每个间隔,并将它们都视为重叠间隔。例如,两个间隔(1, 3)(2, 4)OP最初提出的问题相互重叠,因此在这种情况下有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)被计数两次,我使用了Set仅保留唯一的Interval对象的Java 。

我的解决方案遵循此轮廓。

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

总运行时间为O(N lg N)。它需要一个输出Set of size O(N)

实作

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

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]

1

如果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)时间


0

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

我搜索发现此实现

布雷特

[edit]重读问题,这不是您的要求。我认为这是最好的实现方式,例如,您已经在会议室中安排了一系列会议(例如,已添加到树中)的会议列表,并且想要查找具有新开始时间和持续时间的会议仍可使用的会议室(搜索字词)。希望该解决方案具有一定的相关性。


0

这个问题可以简化为元素唯一性问题。

元素唯一性具有Omega(n log n)下界(计算比较数),因此您不能做得更好。


6
提供答案时,您应该清晰明确。我不确定删除的帖子是什么样子,但是在这篇文章中,您至少可以告诉人们如何将元素唯一性减少为间隔重叠。
miushock 2014年

-1

您可以浏览列表一次,并保留到目前为止遇到的所有间隔的哈希表。如果输入间隔是哈希表某个间隔的一部分,则将其合并到哈希表间隔中。标记非原始间隔(合并的间隔包含一个以上的间隔)。

然后,您第二次遍历该列表,并针对哈希表中的每个间隔检查它是否包含在合并的间隔中。

我不知道它是否为O(N),但比O(N ^ 2)好得多。


4
唯一的问题是,哈希表不支持“区间交集”操作:)
Nikita Rybak 2010年

您是指哈希表的某些特定实现吗?因为我在谈论这个概念。您始终可以自己执行操作。
伊利亚·科根

@IlyaKogan我认为Nikita试图说明的一点是,没有明显的方法可以快速找到与给定查询间隔相交的间隔。天真的方法每个查询将花费O(n)时间,这将是O(n ^ 2)算法。您可以使用间隔树,但这与哈希表根本不相关。
John Kurlak 2014年

@JohnKurlak同意。好像我错过了这一部分。
Ilya Kogan 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.