合并区间范围

Posted

技术标签:

【中文标题】合并区间范围【英文标题】:Merge ranges in intervals 【发布时间】:2013-01-27 08:27:02 【问题描述】:

给定一组区间:1-4, 6-7, 10-12 添加一个新区间:(9,11),以便“合并”最终解决方案:输出:1-4, 6 -7, 9-12。合并可以发生在双方(低范围和高范围)。

我看到这个问题在多个地方得到了回答,甚至有人建议使用 Interval Tress,但没有解释他们将如何使用它。我知道的唯一解决方案是按开始时间的升序排列间隔并迭代它们并尝试适当地合并它们。

如果有人可以帮助我了解我们如何在这个用例中使用区间树,那就太好了!

[我一直在关注 CLRS 书中的区间树,但他们没有谈论合并,他们谈论的只是插入和搜索。]

【问题讨论】:

这个答案:***.com/a/6462731/1296661 提到了合并区间树的算法 【参考方案1】:

(我假设这意味着间隔永远不会重叠,否则它们会被合并。)

做到这一点的一种方法是存储一个平衡的二叉搜索树,一个范围的每个端点都有一个节点。然后将每个节点标记为标记间隔开始的“打开”节点或标记间隔结束的“关闭”节点。

当插入一个新的范围时,关于范围的起点会出现以下两种情况之一:

    它已经在一个范围内,这意味着您将扩展一个已经存在的范围作为插入的一部分。 它不在范围内,因此您将创建一个新的“开放”节点。

要确定您所处的情况,您可以在树中进行先行搜索以查找范围的起点。如果您获得 NULL 或关闭节点,则需要插入一个表示范围起点的新打开节点。如果你得到一个开放的节点,你将继续延长这个间隔。

从那里,您需要确定范围延伸多远。为此,请不断计算您插入的初始节点的后继节点,直到发生以下情况之一:

    您已经查看了树中的所有节点。在这种情况下,您需要插入一个关闭节点来标记此间隔的结束。

    您会在范围结束后看到一个关闭节点。在这种情况下,当新范围结束时,您处于现有范围的中间,因此您无需再做任何事情。大功告成。

    您会在范围结束之前看到一个关闭或打开的节点。在这种情况下,您需要从树中删除该节点,因为旧范围已被新范围包含。

    您会在范围结束后看到一个打开的节点。在这种情况下,请在树中插入一个新的关闭节点,因为您需要在看到这个新节点的开始之前终止当前范围。

天真地实现,该算法的运行时间为 O(log n + k log n),其中 n 是区间数,k 是在此过程中删除的区间数(因为您必须执行 n 次删除)。但是,您可以使用以下技巧将其加速到 O(log n)。由于删除过程总是按顺序删除节点,因此您可以使用后继搜索端点来确定要删除的范围的末尾。然后,您可以通过执行两个树拆分操作和一个树连接操作来拼接子范围以从树中删除。在合适的平衡树(例如红黑树或 splay)上,这可以在 O(log n) 总时间内完成,如果要包含很多范围,这会更快。

希望这会有所帮助!

【讨论】:

对于起始点插入情况:在寻找前驱之前,首先检查具有相同值的节点。如果它存在并且是一个“打开”节点,你就完成了(对于起点什么都不做),如果它是一个“关闭”节点,你需要删除那个节点(间隔正在延长)。【参考方案2】:

公共类 MergeIntervals

public static class Interval 

    public double start;
    public double end;

    public Interval(double start, double end)
        this.start = start;
        this.end = end;
    


public static List<Interval> mergeInteval(List<Interval> nonOverlapInt, Interval another)

    List<Interval> merge = new ArrayList<>();

    for (Interval current : nonOverlapInt)

        if(current.end < another.start || another.end < current.start)
            merge.add(current);
        
        else

            another.start = current.start < another.start ? current.start : another.start ;
            another.end = current.end < another.end ? another.end : current.end;                
                   
    
    merge.add(another);
    return merge;   

【讨论】:

【参考方案3】:

看看这个。它可以帮助你:- http://www.boost.org/doc/libs/1_46_0/libs/icl/doc/html/index.html

该库提供以下功能:

1) 间隔集

2) 分离间隔设置

3) split_interval_set

【讨论】:

谢谢,但我对算法而不是现成的库/API 更感兴趣。 一个简单的算法可能是,您可以首先按起始值对范围进行排序,然后从头到尾迭代范围,每当找到与下一个重叠的范围时,将它们合并 是的,我已经知道了。引用我的问题:“我知道的唯一解决方案是按开始时间的升序排列间隔并迭代它们并尝试适当地合并它们。”。我正在寻找更优化的解决方案(可能涉及区间树?)【参考方案4】:

C#

public class Interval

    public Interval(int start, int end)  this.start = start; this.end = end; 
    public int start;
    public int end;


void AddInterval(List<Interval> list, Interval interval)

    int lo = 0;
    int hi = 0;
    for (lo = 0; lo < list.Count; lo++)
    
        if (interval.start < list[lo].start)
        
            list.Insert(lo, interval);
            hi++;
            break;
        
        if (interval.start >= list[lo].start && interval.start <= list[lo].end)
        
            break;
        
    
    if (lo == list.Count)
    
        list.Add(interval);
        return;
    

    for (hi = hi + lo; hi < list.Count; hi++)
    
        if (interval.end < list[hi].start)
        
            hi--;
            break;
        
        if (interval.end >= list[hi].start && interval.end <= list[hi].end)
        
            break;
        
    
    if (hi == list.Count)
    
        hi = list.Count - 1;
    

    list[lo].start = Math.Min(interval.start, list[lo].start);
    list[lo].end = Math.Max(interval.end, list[hi].end);

    if (hi - lo > 0)
    
        list.RemoveRange(lo + 1, hi - lo);
    

【讨论】:

【参考方案5】:

只需将相关区间添加到区间集的末尾,然后对区间集的所有元素执行合并即可。

合并操作在这里详细介绍:http://www.geeksforgeeks.org/merging-intervals/

如果你不喜欢 C++ 代码,这里是 python 中的相同内容:

def mergeIntervals(self, intervalSet):
    # interval set is an array.
    # each interval is a dict w/ keys: startTime, endTime.  
    # algorithm from: http://www.geeksforgeeks.org/merging-intervals/
    import copy
    intArray = copy.copy(intervalSet)
    if len(intArray) <= 1:
        return intArray
    intArray.sort(key=lambda x: x.get('startTime'))
    print "sorted array: %s" % (intArray)
    myStack = []  #append and pop.
    myStack.append(intArray[0])
    for i in range(1, len(intArray)):
        top = myStack[0]
        # if current interval NOT overlapping with stack top, push it on.
        if   (top['endTime'] < intArray[i]['startTime']):
            myStack.append(intArray[i])
        # otherwise, if end of current is more, update top's endTime
        elif (top['endTime'] < intArray[i]['endTime']):
            top['endTime'] = intArray[i]['endTime']
            myStack.pop()
            myStack.append(top)

    print "merged array: %s" % (myStack)
    return myStack

不要忘记你的鼻子测试来验证你确实做对了:

class TestMyStuff(unittest.TestCase):

    def test_mergeIntervals(self):
        t = [  'startTime' : 33, 'endTime' : 35 ,  'startTime' : 11, 'endTime' : 15  ,  'startTime' : 72, 'endTime' : 76 ,  'startTime' : 44, 'endTime' : 46   ]
        mgs = MyClassWithMergeIntervalsMethod()
        res = mgs.mergeIntervals(t)
        assert res == [  'startTime' : 11, 'endTime' : 15  ,  'startTime' : 33, 'endTime' : 35 ,  'startTime' : 44, 'endTime' : 46  ,  'startTime' : 72, 'endTime' : 76  ]

        t = [  'startTime' : 33, 'endTime' : 36 ,  'startTime' : 11, 'endTime' : 35  ,  'startTime' : 72, 'endTime' : 76 ,  'startTime' : 44, 'endTime' : 46   ]
        mgs = MyClassWithMergeIntervalsMethod()
        res = mgs.mergeIntervals(t)
        assert res == ['endTime': 36, 'startTime': 11, 'endTime': 46, 'startTime': 44, 'endTime': 76, 'startTime': 72]

【讨论】:

以上是关于合并区间范围的主要内容,如果未能解决你的问题,请参考以下文章

区间合并

尾递归做区间合并插入示例

803. 区间合并(贪心)

区间合并---输出合并完成后的区间个数

区间合并---输出合并完成后的区间个数

使用 Spark Scala 进行区间合并