Объединить диапазоны интервалов

Учитывая набор интервалов: {1-4, 6-7, 10-12}, добавьте новый интервал: (9,11), чтобы окончательное решение было "объединено": Выход: {1-4, 6 -7, 9-12}. Слияние может происходить с обеих сторон (как с низким, так и с высоким диапазоном).

Я видел, что на этот вопрос был дан ответ в нескольких местах, кто-то даже предложил использовать Interval Tress, но не объяснил, как именно они будут его использовать. Единственное решение, о котором я знаю, это упорядочить интервалы в порядке возрастания их времени начала и повторить их и попытаться слить их соответствующим образом.

Если кто-то может помочь мне понять, как мы можем использовать интервальные деревья в этом случае, это будет здорово!

[Я следил за деревьями интервалов в книге CLRS, но они не говорят о слиянии, все, о чем они говорят, это вставка и поиск.]

Ответ 1

(Я предполагаю, что это означает, что интервалы никогда не могут пересекаться, поскольку в противном случае они будут объединены.)

Один из способов сделать это - хранить сбалансированное двоичное дерево поиска с одним node на конечную точку диапазона. Каждый node будет помечен как "открытый" node, обозначающий начало интервала или "закрыть" node, обозначающий конец интервала.

При вставке нового диапазона произойдет один из двух случаев относительно начальной точки диапазона:

  • Это уже внутри диапазона, что означает, что вы расширите уже существующий диапазон как часть вставки.
  • Это не внутри диапазона, поэтому вы создадите новый "открытый" node.

Чтобы определить, в каком случае вы находитесь, вы можете выполнить поиск предшественника в дереве для начальной точки диапазона. Если вы получите NULL или закрыть node, вам нужно вставить новый открытый node, представляющий начальную точку диапазона. Если вы откроете открытый node, вы просто продолжите этот интервал.

Оттуда вам нужно определить, как далеко простирается диапазон. Чтобы сделать это, непрерывно вычисляйте преемника начального node, который вы вставили, до тех пор, пока не произойдет одно из следующих событий:

  • Вы просмотрели все узлы в дереве. В этом случае вам нужно вставить конец node, обозначающий конец этого интервала.

  • После окончания диапазона вы увидите закрытие node. В этом случае вы находитесь в середине существующего диапазона, когда заканчивается новый диапазон, поэтому вам больше не нужно ничего делать. Все готово.

  • Вы увидите закрытие или открытие node до конца диапазона. В этом случае вам нужно удалить этот node из дерева, так как старый диапазон добавляется новым.

  • После окончания диапазона вы видите открытый node. В этом случае вставьте новый тег node в дерево, так как вам нужно завершить текущий диапазон, прежде чем увидеть начало этого нового.

Реализовано наивно, время выполнения этого алгоритма - 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;   
}

Ответ 4

С#

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/

Если вы не в настроении для кода на С++, вот что такое в 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 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}]