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

时间:2010-12-28 00:33:04

标签: algorithm

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

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

11 个答案:

答案 0 :(得分:92)

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

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 :(得分:30)

按起点对间隔进行排序。然后折叠该列表,如果它们重叠,则将每个间隔与其邻居(即(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)]

答案 2 :(得分:7)

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

答案 3 :(得分:4)

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

所以你会先得到:

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

因为4(最大)&lt; 5和10&lt; 12,{5,10}被隔离。

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

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

答案 4 :(得分:2)

假设起点和终点之间的差异很小,比如说&lt;例如。 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是保持到目前为止看到的间隔的运行按位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)

  • 中间隔的位模式

答案 5 :(得分: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)时间

答案 6 :(得分:1)

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

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

答案 7 :(得分:0)

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

我搜索了this implementation

布雷特

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

答案 8 :(得分:0)

这是Python中的简单O(N*log(N))实现:

def overlapping(intervals):
    last = (-1, -1)
    overlapping = set()

    for curr in sorted(intervals, key=lambda p: p[0]):
        if curr[0] < last[1]:
            overlapping.add(curr)
            overlapping.add(last)
        last = max(curr, last, key=lambda p: p[1])

    return list(overlapping - set((-1, -1)))

print overlapping([(1, 3), (12, 14), (2, 4), (13, 15), (5, 10)])
#=> [(1, 3), (13, 15), (2, 4), (12, 14)]

首先,它按时间间隔对间隔进行排序,而不是每个间隔将初始时间与找到的最大结束时间进行比较,如果它更小,则表示存在重叠。

排序部分是需要最多时间的部分,因此最终的复杂性为N*log(N)

答案 9 :(得分:0)

这是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)被计数两次,我使用了Java Set,该Java仅保留唯一的Interval对象。

我的解决方案遵循此概述。

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

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

实施

为了向集合中添加间隔,我按预期创建了一个自定义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]

答案 10 :(得分:-1)

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

然后你第二次浏览列表,并且对于每个间隔检查哈希表是否包含在合并的间隔中。

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