让每个间隔
i 表示为
s_i,e_i
(start_i,end_i)。
我可以展示算法,它的时间复杂度是
O(nlogk)
,其中
k
是与某些
i 相交的最大间隔数目,即区间(
s_i,s_{i+1}
)。
在最坏情况下,
k
是在
O(n)
中,但它确实提高了更分散间隔的性能。
我们将使用一个最小堆来存储间隔,同时迭代列表,最小堆将根据结束值(
e_i
)进行排序。
这个想法是通过升序开始迭代列表,计算被看到的间隔的数量,但是结束值却比间隔高。
伪代码(带有解释):
h = new min heap //sorted by end value
h.push (-infinity,infinity) //add a dummy interval for avoiding dealing with empty heap cases
res = 0
for each interval (s_i,e_i) in ascending order of s_i:
//push out all already "expired" intervals:
while (heap.min() < s_i):
heap.pop()
// at this point, all intervals in the heap:
// 1. started before s_i
// 2. finish after s_i
// thus, each of them is intersecting with current interval.
res = res + heap.size() - 1 //-1 for removing dummy interval (-inf,inf)
heap.push(e_i)
return res
时间复杂度:
- 每个步骤中堆的大小最多为
k
(如上定义)。
- 每个区间只被推入和移除一次,每次操作的时间复杂度为O(logk)。
- 这样总的时间复杂度为
O(nlogk)
。
正确性:
声明:
两个区间(s_i,e_i)和(s_j,e_j)相交,当且仅当:
s_i <= s_j <= e_i OR s_j <= s_i <= e_j
通过检查2个区间的所有可能性(由于 s_i<=e_i, s_j<=e_j
,我们有 4!/(2!2!)=6
种可能性),证明很简单。
(1) s_i <= e_i <= s_j <= e_j - no overlap
(2) s_j <= e_j <= s_i <= e_j - no overlap
(3) s_j <= s_i <= e_i <= e_j - overlap, and condition meets
(4) s_i <= s_j <= e_j <= e_j - overlap, and condition meets
(5) s_j <= s_i <= e_j <= e_i - overlap, and condition meets
(6) s_i <= s_j <= e_i <= e_j - overlap, and condition meets
回到证明:
因此,我们知道如果两个区间相交,在遇到第二个区间(假设为(s_i,e_i)
)时,第一个区间(s_j,e_j)
仍然在堆中,因为s_i <= e_j
,并且我们将(s_i,e_i)
与(s_j,e_j)
的交集添加到计数中。我们知道这也是正确的插入,因为我们已经看到了s_j
,所以我们知道s_j <= e_j <= s_i
,根据上述声明-这确实是一个相交的区间。
此外,由于对于每个相交的区间(s_i,e_i)
和(s_j,e_j)
,我们保证在处理(s_i,e_i)
时(s_j,e_j)
仍然在堆中(来自上述声明,并且因为我们永远不会将其删除,因为对于每个k
我们已经处理:s_k <= s_i <= e_j -> e_j >= s_k
),因此当我们在第二个区间遍历中添加堆的大小时,保证将计算(s_j,e_j)
和(s_i,e_i)
的交集。
证毕
小假设:不确定这是否能处理重复,应该通过仔细查看<
和<=
比较来处理这些边缘情况。
Python代码:
intervals = [(0,3.5),(1,2),(1.5,2.5),(2.1,3),(4,5)]
def findNumOverlapping(intervals):
import heapq
h = []
heapq.heappush(h, 10000)
res = 0
for (s,e) in intervals:
while (heapq.nsmallest(1, h)[0] < s):
heapq.heappop(h)
res = res + len(h) - 1
heapq.heappush(h,e)
return res