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].
public class MergingRanges {
public static class Interval {
int start;
int end;
public Interval(int start, int end) {
this.start = start;
this.end = end;
}
public static Interval newInterval(int start, int end) {
return new Interval(start, end);
}
@Override
public int hashCode() {
return Objects.hash(start, end);
}
@Override
public boolean equals(Object o) {
if (o instanceof Interval) {
Interval other = (Interval) o;
return this.start == other.start && this.end == other.end;
}
return false;
}
public String toString() {
return new StringBuilder("(start").append(start).append(",end=").append(end).append(")").toString();
}
}
public static List<Interval> mergeRanges(List<Interval> intervals) {
if (intervals.size() < 1) {
throw new IllegalArgumentException();
}
intervals.sort(Comparator.comparingInt(o -> o.start));
List<Interval> mergedIntervals = new ArrayList<>();
Interval pastInterval = intervals.get(0);
for (int i = 1; i < intervals.size(); i++) {
Interval currentInterval = intervals.get(i);
if (currentInterval.start <= pastInterval.end) {
// if the past interval can be merged with the current interval
if (currentInterval.end > pastInterval.end) {
// this means currentInterval finishes outside of the past-intervals limit
Interval newInterval = new Interval(pastInterval.start, currentInterval.end);
pastInterval = newInterval;
}
} else {
// as the past interval cannot be merged within the current interval, its the beginning of new interval
mergedIntervals.add(pastInterval);
pastInterval = currentInterval;
}
}
mergedIntervals.add(pastInterval);
return mergedIntervals;
}
}
public class MergingRangesTest {
@Test
public void shouldBeMergedWhenTheyJustTouchesTheBoundary() {
//
// [Meeting(1, 2), Meeting(2, 3)]
// These meetings should be merged, although they don't exactly "overlap"—they just "touch."
//
List<Interval> mergedIntervals = MergingRanges.mergeRanges(Arrays.asList(newInterval(1, 2), newInterval(2, 3)));
assertEquals(newInterval(1, 3), mergedIntervals.get(0));
}
@Test
public void shouldBeCalculatedCorrectlyWhenOneIntervalIsSubsumedByTheOther() {
//
// Notice that although the second meeting starts later, it ends before the first meeting ends.
// Does your method correctly handle the case where a later meeting is "subsumed by" an earlier meeting?
//
List<Interval> mergedIntervals = MergingRanges.mergeRanges(Arrays.asList(newInterval(1, 5), newInterval(2, 3)));
assertEquals(newInterval(1, 5), mergedIntervals.get(0));
// here (1,10) contains the rest of the interval, can we handle this?
mergedIntervals = MergingRanges.mergeRanges(Arrays.asList(newInterval(1, 10), newInterval(2, 6), newInterval(3, 5), newInterval(7, 9)));
assertEquals(newInterval(1, 10), mergedIntervals.get(0));
}
@Test
public void mergeRanges() throws Exception {
List<Interval> mergedIntervals = MergingRanges.mergeRanges(Arrays.asList(newInterval(0, 1), newInterval(3, 5), newInterval(4, 8), newInterval(10, 12), newInterval(9, 10)));
// [Meeting(0, 1), Meeting(3, 8), Meeting(9, 12)]
assertEquals(newInterval(0, 1), mergedIntervals.get(0));
assertEquals(newInterval(3, 8), mergedIntervals.get(1));
assertEquals(newInterval(9, 12), mergedIntervals.get(2));
}
}