算法分析与设计,第10周博客
57. Insert Interval
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.
Example 1:
Given intervals [1,3],[6,9]
, insert and merge [2,5]
in as [1,5],[6,9]
.
Example 2:
Given [1,2],[3,5],[6,7],[8,10],[12,16]
, insert and merge [4,9]
in as [1,2],[3,10],[12,16]
.
This is because the new interval [4,9]
overlaps with [3,5],[6,7],[8,10]
.
把它放到合适位置,并且如果需要的话,把有叠加的区间合并。
先来看区间的定义,很简单,两个整数,分别指出区间的开始值和结束值:
/**
* Definition for an interval.
* struct Interval {
* int start;
* int end;
* Interval() : start(0), end(0) {}
* Interval(int s, int e) : start(s), end(e) {}
* };
*/
那么,再来看下怎么样的两个区间需要合并,设有两个区间a和b,并且a.start <= b.start:
那么,当 b.start <= a.end时,两个区间需要合并,并且合并后的区间c有
c.start = a.start,c.end = max(a.end, b.end);
这样,就把基本的概念都搞清楚了。
那么,面对这个问题的解决办法呢?
首先,对整个数组进行分组,整个数组可以分成三个部分:
- 位于插入区间之前的部分。这个部分的区间可以原封不动的加入结果中。
- 与插入区间有交集的部分。这个部分需要整合成一个新的区间,然后加入到结果中。
- 位于插入区间之后的部分。这个部分的区间也可以原封不动的加入到结果中。
- p.start >= i.start && p.start <= i.end
- p.end >= j.start && p.end <= j.end
那么,只要用p.start 和p.end 分别作为搜索值,使用二分查找法,找出的下标就分别是 i 和 j 。这样,这个问题就得到解决了。
二分查找的函数如下,这个函数返回的是intervals中start值第一个小于target值的interval,但是它不保证其end会大于target:
int search(vector<Interval>& intervals, int low, int high, int target) {
while (low < high) {
int mid = low+((high-low+1)>>1);
if (intervals[mid].start == target) {
return mid;
} else if (intervals[mid].start > target) {
high = mid-1;
} else {
if (mid == low)
return low;
low = mid;
}
}
return low;
}
因为上面函数的稍微的特殊性,所以需要注意,返回的值所代表的区间是否和插入区间有交集。如果没有交集,那么需要把这个区间也单独加入到结果中。
vector<Interval> insert(vector<Interval>& intervals, Interval newInterval) {
vector<Interval> res;
if (intervals.size() == 0) {
res.push_back(newInterval);
return res;
}
int start = search(intervals, 0, intervals.size()-1, newInterval.start);
int end = search(intervals, 0, intervals.size()-1, newInterval.end);
res.insert(res.end(), intervals.begin(), intervals.begin()+start);
Interval mid;
mid.start = intervals[start].start < newInterval.start ? intervals[start].start : newInterval.start;
if (intervals[start].end < newInterval.start) {
res.push_back(intervals[start]);
mid.start = newInterval.start;
}
mid.end = intervals[end].end >= newInterval.end ? intervals[end].end : newInterval.end;
res.push_back(mid);
if (intervals[end].start > newInterval.end) {
res.back().end = newInterval.end;
res.push_back(intervals[end]);
}
res.insert(res.end(), intervals.begin()+end+1, intervals.end());
return res;
}
下面来看下这个函数的时间复杂度,首先有用两次二分查找法,时间复杂度是O(log(n)),然后需要把原来数组中的区间加到结果中去,时间复杂度是O(n),所以总体的时间复杂度也还是O(n)。这也是无法避免的,因为需要把原数组的元素添加到新的数组中去,我们利用了二分查找法来减轻了一些时间复杂度,把从i到j的时间复杂度从O(j-i)变成了O(log(n)),所以当插入区间的跨度越大时,这个方法的效率就越高。