查找重叠的集合的间隔

时间:2013-09-30 03:14:39

标签: algorithm data-structures

所以我有一个包含间隔端点的集合。例如,

Set s = {(1,4),(3,7),(5,8),(14,17),(0,2),(11,14)}

我需要一种方法来找出有多少重叠间隔。在上面的例子中,答案是5,因为

(1,4) --> (3,7), (0,2)
(3,7) --> (5,8),(0,2)
(5,8) --> 
(14,17) --> (11,14)

我需要一种算法,需要O(N log N)时间来找出总和。现在,如果我对所有起点进行排序并在每个点Find number range intersection上应用此处建议的答案,我会得到一个O(N ^ 2)解。除了集合之外我还需要什么样的数据结构?谢谢!

5 个答案:

答案 0 :(得分:4)

为每个区间[a,b]构建值列表(a,-1),(b,1)。现在对这些进行排序可让您遍历数组,计算每个端点当前打开的间隔数,只需聚合+1和-1即可。

重要的是(b,1)在排序列表中位于(b,-1)之后;因为即使交叉点在一个点上,我们也想计算交点。

此处填写完整的代码。

def overlaps(intervals):
    es = []
    for a, b in intervals:
        es.append((a, -1))
        es.append((b, 1))
    es.sort()
    result = 0
    n = 0
    for a, s in es:
        if s == -1: result += n
        n -= s
    return result

注意,这通常是O(n log n),并且不需要任何花哨的数据结构。

答案 1 :(得分:2)

首先,我假设您的示例(0,1)(1,2)重叠。

顺便说一句,您的示例不正确,(3,7)(0,2)不重叠

解决此问题的一种方法是:

  1. 根据起点对间隔进行排序
  2. 从最低起点到最高点迭代
    2A。计算比当前起点更大或等于的先前端点的数量 2B。在当前终点的计数中添加一个。
  3. 第1步可以在O(n log n)中完成 步骤2在进行计数时迭代所有间隔。所以它是O(n * X),其中X是进行计数的复杂性。使用Fenwick Tree,我们可以在O(log n) 1 中执行此操作,因此步骤2也可以在O(n log n)中完成。

    因此整体复杂度为O(n log n)

    伪代码:

    def fenwick_tree.get(num):
        return the sum from counter[0] to counter[num]
    
    def fenwick_tree.add(num, val):
        add one to counter[num]
    
    intervals = [...]
    sort(intervals) # using the starting point as the key
    init_fenwick_tree()
    sum = 0
    count = 0
    for (starting_point, end_point) in intervals:
        sum = sum + (count - fenwick_tree.get(starting_point-1))
        fenwick_tree.add(end_point,1)
    return sum
    

    Python代码(仅在间隔点为非负整数时才有效):

    MAX_VALUE = 2**20-1
    f_arr = [0]*MAX_VALUE
    
    def reset():
        global f_arr, MAX_VALUE
        f_arr[:] = [0]*MAX_VALUE
    
    def update(idx,val):
        global f_arr
        while idx<MAX_VALUE:
            f_arr[idx]+=val
            idx += (idx & -idx)
    
    def read(idx):
        global f_arr
        if idx <= 0:
            return 0
        result = 0
        while idx > 0:
            result += f_arr[idx]
            idx -= (idx & -idx)
        return result
    
    intervals = [(1,4),(3,7),(5,8),(14,17),(0,2),(11,14)]
    intervals = sorted(intervals, key=lambda x: x[0])
    reset()
    total = 0
    for processed, interval in enumerate(intervals):
        (start, end) = interval
        total += processed - read(start-1)
        update(end, 1)
    print total
    

    将打印来自这些重叠的4

    (0,2) - (1,4)
    (1,4) - (3,7)
    (3,7) - (5,8)
    (11,14) - (14,17)
    

    请注意,我们无法获得重叠间隔,因为在最坏的情况下会出现O(n^2)重叠,无法在O(n log n)时间内打印。

    1 实际上,Fenwick树在O(log M)时间内进行计数,其中M是区间中最大可能的不同值。请注意M <= 2n,因为只有很多不同的值。因此,说Fenwick Tree在O(log n)时间进行计数也是正确的。

答案 2 :(得分:1)

快速提示:首先对间隔进行排序。现在查看您的间隔,将每个添加到端点排序的最小堆。对于每个间隔,从堆中删除小于该间隔开始点的所有内容。堆中剩余的每个端点表示在此间隔之前开始并且与其重叠的间隔,因此按照堆的大小递增overlaps。现在将当前间隔添加到堆中并继续。

在伪代码中:

Sort(intervals)
(firstStart, firstEnd) = intervals[0]
currentIntervals = MinHeap()
overlaps = 0

for each (start, end) in intervals[1:]:
  remove from currentIntervals all numbers < start
  overlaps += Size(currentIntervals)
  HeapPush(end, currentIntervals)
return overlaps

我没有测试过这个,但似乎至少看似合理。

答案 3 :(得分:0)

这可以简单地使用贪婪技术来完成。 只需按照以下步骤操作:

根据起点对间隔进行排序 从最低起点到最高点迭代 计算大于或等于当前起始点的先前端点的数量。 增加当前终点的计数。

答案 4 :(得分:0)

保罗的回答非常聪明。如果是采访,我认为我不能提出这个想法。这里我有另一个版本,也是O(nlog(n))。

import heapq

def countIntervals(s):
    s.sort()
    end = [s[0][1]]
    res, cur = 0, 0
    for l in s[1:]:
        if l[0]>heapq.nsmallest(1, end)[0]:
            heapq.heappop(end)
        cur = len(end)
        res += cur
        end.append(l[1])
    return res

我们维护一个存储即将到来的端点的最小堆。每当新的间隔进入时,我们应该将它的起点与最小的终点进行比较。

如果起点较大,则表示最小的终点(表示该间隔)永远不会导致更多重叠。因此我们把它弹出来。

如果起点较小,则表示所有终点(相应的间隔)与新的间隔重叠。

然后,终点数(“cur”)是这个新的未来区间带来的重叠数。在我们更新结果之后,我们将新的即将到来的间隔的端点添加到堆中。