LeetCode – Merge Intervals

Given a collection of intervals, merge all overlapping intervals.

For example,
Given [1,3],[2,6],[8,10],[15,18],
return [1,6],[8,10],[15,18].

Analysis

The key to solve this problem is defining a Comparator first to sort the arraylist of Intevals.

Java Solution

public List<Interval> merge(List<Interval> intervals) {
    List<Interval> result = new ArrayList<Interval>();
 
    if(intervals==null||intervals.size()==0)
        return result;
 
    Collections.sort(intervals, new Comparator<Interval>(){
        public int compare(Interval i1, Interval i2){
            if(i1.start!=i2.start)
                return i1.start-i2.start;
            else
                return i1.end-i2.end;
        }
    });
 
    Interval pre = intervals.get(0);
    for(int i=0; i<intervals.size(); i++){
        Interval curr = intervals.get(i);
        if(curr.start>pre.end){
            result.add(pre);
            pre = curr;
        }else{
            Interval merged = new Interval(pre.start, Math.max(pre.end, curr.end));
            pre = merged;
        }
    }
    result.add(pre);
 
    return result;
}
Category >> Algorithms  
If you want someone to read your code, please put the code inside <pre><code> and </code></pre> tags. For example:
<pre><code> 
String foo = "bar";
</code></pre>
  • Ashwin Rayaprolu

    This would be one of the best solution as i merge while performing sort operation. So no additional sort required. Let me know if any one has any better solution

    /**
    * 1. For the original DataNode2 we need to make sure we merge nodes when ever we see overlap
    *
    * @author Ashwin Rayaprolu
    *
    */
    public class MergeInsertIntervals {

    /**
    * @param args
    */
    public static void main(String[] args) {
    int[][] originalIntervals = { { 94230, 94299 }, { 94289, 94699 }, { 94200, 94240 }, { 94133, 94133 } };
    // Sort our array based on lower bound number
    DataNode2LinkedList2 linkedList = new DataNode2LinkedList2();
    //O(n log n) operation
    for(int[] data:originalIntervals){
    linkedList.insert(new DataNode2(data));
    }

    System.out.println(“———Sorted?merged Intervals———-“);
    int[][] sortedIntervals = linkedList.traverse();

    System.out.println(“———Merged Intervals———-“);

    }

    }

    /**
    * @author Ashwin Rayaprolu
    *
    */
    class DataNode2LinkedList2 {
    DataNode2 firstNode;
    DataNode2 lastNode;

    int size = 0;
    // O(log n) operation
    void insert(DataNode2 newNode) {
    if (firstNode == null) {
    firstNode = newNode;
    lastNode = newNode;
    return;
    }

    // Keep interval on left if lower bound is < than tempPointer
    DataNode2 tempPointer = firstNode;

    if (newNode.data[0] < tempPointer.data[0]) {
    while (tempPointer.leftPointer != null && newNode.data[0] =tempPointer.data[0]){
    //tempPointer.data[1]=
    tempPointer.data[0] = newNode.data[0];
    return;
    }

    newNode.rightPointer = tempPointer;

    if (tempPointer.leftPointer == null) {
    firstNode = newNode;
    }

    tempPointer.leftPointer = newNode;
    ++size;

    } else {
    while (tempPointer.rightPointer != null && newNode.data[0] >= tempPointer.data[0]) {
    tempPointer = tempPointer.rightPointer;
    }

    //If new node is overlapping then merge with current node and return
    if(tempPointer.data[1]>=newNode.data[0]){
    //tempPointer.data[1]=
    tempPointer.data[1] = newNode.data[1];
    return;
    }

    newNode.leftPointer = tempPointer;

    if (tempPointer.rightPointer == null) {
    lastNode = newNode;
    }

    tempPointer.rightPointer = newNode;
    ++size;

    }

    }

    int[][] traverse() {
    DataNode2 tempPointer = firstNode;
    int[][] sortedArray = new int[size + 1][2];
    int index = 0;
    while (tempPointer != null) {
    sortedArray[index] = tempPointer.data;
    ++index;
    System.out.println(“{” + tempPointer.data[0] + “,” + tempPointer.data[1] + “}”);
    tempPointer = tempPointer.rightPointer;
    }
    return sortedArray;
    }
    }

    /**
    * Data Node used for sorting
    *
    * @author Ashwin Rayaprolu
    *
    */
    class DataNode2 {
    int[] data = {};
    DataNode2 leftPointer;
    DataNode2 rightPointer;

    public DataNode2(int[] data) {
    this.data = data;
    }

    }

  • Matias SM

    Just a comment (for extra points in an interview and to improve the proposed solution). The proposed comparator implementation:

    public int compare(Interval i1, Interval i2) {
    return i1.start - i2.start;
    }

    is discouraged since nowhere it is said that start must be positive and that logic may cause under/overflow.

    It is better to use Integer.compare (https://docs.oracle.com/javase/7/docs/api/java/lang/Integer.html#compare(int,%20int))

  • Matias SM

    This is a guaranteed Time complexity O(N log S) where N is the length of the input and S is the number of different starting (interval) values. It requires O(S) space though.

    I think it can be optimized to O(S log S) if we use a hashMap for sToInt and an additional TreeSet (or min heap) to have the starting values ordered increasingly. The space complexity would remain the same (with a higher constant factor).


    class Interval {
    final int s;
    final int e;

    Interval(int s, int e) { this.s = s; this.e = e; }

    /** Assumes there exists an overlap */
    Interval merge(Interval o) {
    return new Interval(Math.min(s, o.s), Math.max(e, o.e));
    }
    }

    Collection merge(List intervals) {
    Map sToInt = new TreeMap();

    for (Interval i : intervals) {
    Interval prev = sToInt.put(i.s, i);
    if (prev != null) { //same start as i, can merge
    sToInt.put(i.s, i.merge(prev));
    }
    }

    //Merge intervals
    if (sToInt.size() <= 1) sToInt.values(); //nothing to merge

    List result = new ArrayList();
    Interval prev = null;
    for (Interval i : sToInt.values()) { //iterate (sorted by s)
    if (prev != null && i.s <= prev.e) {
    prev = prev.merge(i);
    } else {
    if (prev != null) result.add(prev);
    prev = i;
    }
    }

    if (prev != null) result.add(prev);
    return result;
    }

  • Ankit Shah

    Dear Author of this solution, the above code certainly works and thanks for giving the solution, I took liberty to enhance your code which will print and work for the following output as well: Basically the idea is in the second test case if there are more than one merging intervals, you will need to remove the last element from the result and add a new one.

    —————————————————–
    Input: [[8,10], [1,3], [2,6], [15,18]]
    sorted list: [[1,3], [2,6], [8,10], [15,18]]
    merged interval: [[1,6], [8,10], [15,18]]
    —————————————————–
    Input: [[8,10], [1,3], [2,6], [3,9], [15,18]]
    sorted list: [[1,3], [2,6], [3,9], [8,10], [15,18]]
    merged interval: [[1,10], [15,18]]
    —————————————————–

    import java.util.*;
    class Interval {
    int lo;
    int hi;
    Interval() {
    lo = 0;
    hi = 0;
    }

    Interval(int lo, int hi) {
    this.lo = lo;
    this.hi = hi;
    }

    @Override
    public String toString() {
    return "[" + lo + "," + hi + "]";
    }
    }

    public class MergeIntervals {
    public static ArrayList merge(ArrayList list) {
    Collections.sort(list, new Comparator() {
    public int compare(Interval i1, Interval i2) {
    if (i1.lo == i2.lo) {
    return i1.hi - i2.hi;
    }
    return i1.lo - i2.lo;
    }
    });
    System.out.println("Sorted Input: " + list);

    ArrayList result = new ArrayList();
    Interval prev = list.get(0);
    for (int i = 1; i = current.lo) {
    Interval interval = new Interval(prev.lo, Math.max(prev.hi, current.hi));
    prev = interval;
    } else {
    prev = current;
    }
    removeIfExist(result, prev);
    result.add(prev);
    }
    return result;
    }

    private static void removeIfExist(ArrayList result, Interval prev) {
    if (result.size() > 0) {
    Interval existing = result.get(result.size() - 1);
    if (existing.lo == prev.lo) {
    result.remove(result.size() - 1);
    }
    }
    }

    public static void main(String[] args) {
    ArrayList list = new ArrayList();
    list.add(new Interval(8, 10));
    list.add(new Interval(1, 3));
    list.add(new Interval(2, 6));
    list.add(new Interval(15, 18));
    System.out.println("Input: " + list);
    System.out.println("merged interval: " + merge(list));
    System.out.println("-----------------------------------------------------");
    list = new ArrayList();
    list.add(new Interval(8, 10));
    list.add(new Interval(1, 3));
    list.add(new Interval(2, 6));
    list.add(new Interval(3, 9));
    list.add(new Interval(15, 18));
    System.out.println("Input: " + list);
    System.out.println("merged interval: " + merge(list));
    System.out.println("-----------------------------------------------------");
    }
    }

  • Omar Edgardo Lugo S├ínchez

    my solution, im the same lol

    ArrayList input = new ArrayList();

    ArrayList r = new ArrayList();

    //Given [1,3],[2,6],[8,10],[15,18],

    input.add(new invervalo(1,3));

    input.add(new invervalo(8,10));

    input.add(new invervalo(15,18));

    input.add(new invervalo(2,6));

    Collections.sort(input);

    // compare items

    for(int i=0;i=obj2.I){

    obj2.I = obj1.I; // merge invertals

    }else{

    r.add(obj1); // could not be merged so add to Result

    }

    }

    // add last item

    r.add((invervalo)input.get(input.size()-1));

  • Travis Kaufman

    Assuming that each interval is unique (e.g. non-repeated numbers in different intervals, such as in the example above), you can do better than O(nlogn) by using a “bucket sort” like operation on the intervals, such that given an array you assign the interval index to the indices in the array corresponding to each number. You can then iterate through the bucketed array, keeping track of the which intervals are open or closed and only spit out intervals in which the parent interval to be opened is also the one closed. This works because the bucket sort guarantees that the previous number iterated over is less than the current number iterated over, so you never have to compare start and end intervals. This can run in O(n) time/space since bucket sort is linear, although will probably perform bad if one of the interval values is a very large number. Here’s a link to a javascript (es6/es2015) implementation: https://gist.github.com/traviskaufman/08847d4cdf7c42bc4d84

    I think in order to have it support multiple instances of numbers, you could assign interval indices to a list instead of just having one value for each in the bucketed array, and tweak the business logic of the bucketed array iteration to handle this case. Thoughts?

  • melonhead

    In English the logic is, sort them all by their left end. For each as-yet ‘unlumped’ one (‘prev’), lump into it all that come after whose left end is less than our ‘lumping one’s’ right end (we know that ‘prev’s left end is < the later ones' left ends because we're visiting in left-end sorted order). When we've caught all we can with this lumper, output it and the next unlumped interval becomes the new 'prev' ie the lumper.