10

Given a set of intervals: {1-4, 6-7, 10-12} add a new interval: (9,11) so that the final solution is 'merged': Output: {1-4, 6-7, 9-12}. The merger can happen on both sides (low as well as high range).

I saw this question was answered at multiple places, someone even suggested using Interval Tress, but did not explain how exactly they would use it. The only solution I know of is to arrange the intervals in ascending order of their start time and iterating over them and trying to merge them appropriately.

If someone can help me understand how we can use interval trees in this use case, that will be great!

[I have been following interval trees in CLRS book, but they do not talk about merging, all they talk about is insertion and search.]

chema989
  • 3,962
  • 2
  • 20
  • 33
Darth.Vader
  • 5,079
  • 7
  • 50
  • 90

5 Answers5

7

(I'm assuming that this means that intervals can never overlap, since otherwise they'd be merged.)

One way to do this would be to store a balanced binary search tree with one node per endpoint of a range. Each node would then be marked as either an "open" node marking the start of an interval or a "close" node marking the end of an interval.

When inserting a new range, one of two cases will occur regarding the start point of the range:

  1. It's already inside a range, which means that you will extend an already-existing range as part of the insertion.
  2. It's not inside a range, so you'll be creating a new "open" node.

To determine which case you're in, you can do a predecessor search in the tree for the range's start point. If you get NULL or a close node, you need to insert a new open node representing the start point of the range. If you get an open node, you will just keep extending that interval.

From there, you need to determine how far the range extends. To do this, continuously compute the successor of the initial node you inserted until one of the following occurs:

  1. You have looked at all nodes in the tree. In that case, you need to insert a close node marking the end of this interval.

  2. You see a close node after the end of the range. In that case, you're in the middle of an existing range when the new range ends, so you don't need to do anything more. You're done.

  3. You see a close or open node before the end of the range. In that case, you need to remove that node from the tree, since the old range is subsumed by the new one.

  4. You see an open node after the end of the range. In that case, insert a new close node into the tree, since you need to terminate the current range before seeing the start of this new one.

Implemented naively, the runtime of this algorithm is O(log n + k log n), where n is the number of intervals and k is the number of intervals removed during this process (since you have to do n deletes). However, you can speed this up to O(log n) by using the following trick. Since the deletion process always deletes nodes in a sequence, you can use a successor search for the endpoint to determine the end of the range to remove. Then, you can splice the subrange to remove out of the tree by doing two tree split operations and one tree join operation. On a suitable balanced tree (red-black or splay, for example), this can be done in O(log n) total time, which is much faster if a lot of ranges are going to get subsumed.

Hope this helps!

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
  • For the start point insertion case: you first check for a node with the same value before looking for a predecessor. If that exists and is an "open" node, you are done (do nothing for the starting point), if it a "close" node, you need to delete that node (the interval is being extended). – Martijn Pieters Sep 23 '18 at 12:48
1

public class 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;   
}
Gökhan Akduğan
  • 533
  • 1
  • 8
  • 17
0

Check this out. It may help you:- http://www.boost.org/doc/libs/1_46_0/libs/icl/doc/html/index.html

The library offers these functionalities:

1) interval_set

2) separate_interval_set

3) split_interval_set

Rahul Tripathi
  • 168,305
  • 31
  • 280
  • 331
  • Thanks, but I am more interested in the algorithm rather than an off the shelf library/API. – Darth.Vader Jan 27 '13 at 08:34
  • A simple algorithm could be that you can first Sort the ranges by starting values and then Iterate over the ranges from beginning to end, and whenever you find a range that overlaps with the next one, merge them – Rahul Tripathi Jan 27 '13 at 08:36
  • 1
    Yes, I already know about that. Quoting from my question: "The only solution I know of is to arrage the intervals in ascendening order of their start time and iterating over them and trying to merge them appropriately.". I am looking for a more optimal solution (may be involving Interval Trees?) – Darth.Vader Jan 27 '13 at 08:37
0

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);
    }
}
-1

This is simply done by adding the interval in question to the end of the interval set, then performing a merge on all teh elements of the interval set.

The merge operation is well-detailed here: http://www.geeksforgeeks.org/merging-intervals/

If you're not in the mood for C++ code, here is the same things in 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

Don't forget your nosetests to verify you actually did the work right:

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}]
Kevin J. Rice
  • 3,337
  • 2
  • 24
  • 23