【问题标题】:Finding intervals of a set that are overlapping查找一组重叠的区间
【发布时间】:2013-09-30 03:14:38
【问题描述】:

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

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) 解决方案。除了集合之外,我可能需要什么样的数据结构有什么线索吗?谢谢!

【问题讨论】:

  • 这与interval tree有关
  • 等等 -- 为什么(3,7)(0,2) 重叠?

标签: algorithm data-structures


【解决方案1】:

为每个区间 [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 开始并使用 1 结束间隔。你应该使用相反的方式。
  • 好极了,最简单最快的。但它应该是result += n - 1,因为不需要自己计算间隔。编辑:哦,对不起。我也对 -1/+1 感到困惑。 result += n 是正确的
  • 我认为 +1 和 -1 是正确的,结果 += n 是正确的。 n 在结果之后更新,因此间隔从不计入自身。如果您按照问题中给出的间隔运行它,它会产生正确的答案 4。
  • @PaulHankin:考虑间隔:intervals = []; intervals.append((1, 10)); intervals.append((5, 20)); intervals.append((15, 25))。您的代码返回 2,但它应该是 3,因为所有三个间隔都有一些重叠,而不是同时重叠。如果您将最后一个间隔更改为 (12, 25),那么您的代码将返回 3。
【解决方案2】:

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

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

解决这个问题的一种方法是:

  1. 根据起点对区间进行排序
  2. 从最低点到最高点迭代
    2a。计算大于或等于当前起点的先前终点的数量。
    2b。当前终点的计数加一。

第一步可以在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 &lt;= 2n,因为只能有那么多不同的值。所以说 Fenwick Tree 在O(log n) 时间进行计数也是正确的。

【讨论】:

    【解决方案3】:

    快速想法:首先对间隔进行排序。现在检查您的间隔,将每个间隔添加到按端点排序的最小堆中。对于每个间隔,从堆中删除小于该间隔起点的所有内容。堆中剩余的每个端点都代表一个在此间隔之前开始并与它重叠的间隔,因此将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
    

    我没有对此进行测试,但至少看起来是合理的。

    【讨论】:

    • 每个间隔最多只计算一个重叠,其中 OP 希望拥有所有可能的间隔计数。您的算法将产生输出 2 到输入 {(0,3),(0,2),(1,3)},它应该是 3。
    • 啊,好吧。更新为使用堆而不是简单的计数器。
    • 看起来不错,尽管由于堆不平衡,在最坏的情况下它是 O(n^2)。假设这个输入:(0,10), (1,11), (2,12), ..., (10, 20) 堆将是线性的,所以HeapPush 操作将是O(n),导致O(n^2) 总数。您可以使用自平衡二叉树,例如 AVL 树。
    • 我认为这不对。二项式堆不能不平衡,它们总是尽可能浓密。
    • 哎呀,是的,对不起,我把它和二叉搜索树混在一起了。
    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

      保罗的回答真的很聪明。如果是面试,我想我不会想出这个主意。这里我有另一个版本,也是 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”)就是这个新来的区间带来的重叠数。更新结果后,我们将新到来的区间的端点添加到堆中。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-01-28
        • 2022-01-26
        • 1970-01-01
        • 2016-02-05
        • 1970-01-01
        • 2013-08-17
        • 2023-01-10
        • 2014-11-07
        相关资源
        最近更新 更多