Given a set of non-overlapping intervals, insert a new interval into the intervals (merge if necessary). You may assume that the intervals were initially sorted according to their start times.
例如,原来的区间集是[1,2],[3,5],[6,7],[8,10],[12,16],插入的新区间是[4,9],合并后就变成了[1,2],[3,10],[12,16]。解决问题的关键是找到与新区间有重合的区间。一种方法是,找到一个重合的区间后,马上与插入区间合并,然后再看与后面的区间是否重合。例如,按上面的例子,首先找到[3,5]与[4,9]重合,合并成[3,9];然后[3,9]与[6,7]比较,合并后仍是[3,9];[3,9]继续与[8,10]比较,如此前进。这种方法的合并操作次数较多,效率低,写起来也比较麻烦。
实际上由于区间集是有序的,被合并的区间一定是连续的,不可能出现[1,2],[6,7]被合并而[3,5]没被合并的情况。所以我们可以去找合并区间段的起始区间和结束区间。按顺序遍历,第一个找到的end比插入区间的start大的区间,即为起始区间,在它前面的区间都是与插入区间没有重合的,不用作改动。但起始区间也不一定与插入区间重合,要继续看后面的判断。接着遍历,第一个找到的end比插入区间end大的区间为结束区间,在它后面的区间都不可能和插入区间重合了。
找到起始和结束区间后,下一步就要确定合并后区间的start和end了,start应为起始区间和插入区间的start中较小的一个;end的话要比较结束区间的start和插入区间end的大小,如果前者大,说明结束区间与插入区间也是不重合的,合并区间的end等于插入区间end,如果后者大,则合并区间的end等于结束区间的end。
/**
* Definition for an interval.
* struct Interval {
* int start;
* int end;
* Interval() : start(0), end(0) {}
* Interval(int s, int e) : start(s), end(e) {}
* };
*/
class Solution {
public:
vector<Interval> insert(vector<Interval>& intervals, Interval newInterval) {
vector<Interval> v;
int i;
for(i=0; i<intervals.size(); i++) {
if(newInterval.start<=intervals[i].end) {
if(newInterval.start>intervals[i].start) {
newInterval.start = intervals[i].start;
}
break;
}
else v.push_back(intervals[i]);//添加在起始区间前的区间
}
for(; i<intervals.size(); i++) {
if(newInterval.end<intervals[i].end) {
if(newInterval.end>=intervals[i].start) {
newInterval.end = intervals[i].end;
i++;// 结束区间与插入区间重合
}
break;
}
}
v.push_back(newInterval);
for(; i<intervals.size(); i++) {
v.push_back(intervals[i]);// 添加在结束区间后的区间
}
return v;
}
};
代码中我没有新建一个合并区间变量,而是直接修改了newInterval的start和end,并把修改后的newInterval当做合并区间,这样并不影响算法的运行,还能节省一个变量开销。对于返回的结果,如果在原来的vector上直接修改,需要移走一些元素,并在特定位置上插入合并区间,vector的insert/erase方法复杂度与插入/删除位置后的元素个数线性相关,相比之下push_back的复杂度仅为常数时间,所以我选择了新建vector并向其中添加元素。整个算法的时间复杂度为O(n)。
改进方法可以用二分查找来确定起始结束区间,时间复杂度降为O(lgn)。但最后构造新区间集时,仍要遍历整个旧区间集(除了起始结束区间之间的区间),所以整个算法理论上的时间复杂度仍为O(n)。