题目
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:
Input: intervals = [[1,3],[6,9]], newInterval = [2,5]
Output: [[1,5],[6,9]]
Example 2:
Input: intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8]
Output: [[1,2],[3,10],[12,16]]
Explanation: Because the new interval [4,8] overlaps with [3,5],[6,7],[8,10].
Difficulty: Hard
分析
这道题,乍看可以暴力求解,直接遍历一遍数组,找到合适的开始区间和结束区间。时间复杂度是 O ( n ) O(n) O(n) 。
但是考虑到区间的有序性,我们可以进行二分查找,将时间复杂度缩小到 O ( l o g n ) O(logn) O(logn) 。
我们要考虑到起点/终点位置的各种可能性:
-
起点/终点处于某个区间内
-
起点/终点处于两个区间之间
-
起点/终点处于第一个区间之前
-
起点/终点处于最后一个区间之后
这样,会组合出不同的情况。
为了方便表示起点/终点的位置,我们设两个变量 startItv
和 endItv
,它们都是某个区间在区间数组 intervals
里的索引。初始值为 0
和 size - 1
。
进行二分查找,每次找 startItv
和 endItv
的中间区间 medium
,通过比较点和 medium
两端的值,设置新的 startItv
或 endItv
。
查找完后:
-
如果点在第一个区间之前,会有
startItv == -1
-
如果点在最后一个区间之后,会有
startItv == size - 1
和endItv == size
-
startItv == endItv
,表示点在intervals[startItv]
这个区间内 -
startItv != endItv
,表示点在intervals[startItv]
和intervals[endItv]
这两个区间之间
有进行查找、求 startItv
和 endItv
的函数如下:
void find(const vector<Interval>& intervals, int num, int& startItv, int& endItv){
startItv = 0;
endItv = intervals.size() - 1;
int result;
while(1){
int size = endItv - startItv + 1;
int medium = startItv + size / 2;
const Interval& itv1 = intervals[medium];
if(size == 1){
if(num < itv1.start) startItv--;
else if(itv1.end < num) endItv++;
break;
}
const Interval& itv2 = intervals[medium - 1];
if(itv1.start <= num) startItv = medium; // 点在 intervals[medium] 之内或之后
else if(num <= itv2.end) endItv = medium - 1; // 点在 intervals[medium - 1] 之内或之前
else{ // 点在 intervals[medium - 1] 和 intervals[medium] 之间
startItv = medium - 1;
endItv = medium;
break;
}
// cout << num << " : " << startItv << " " << endItv << endl;
}
}
接下来,需要由起点 A
和终点 B
的 startItv
、 endItv
来判断,怎么创造新的区间。
起点要插入的开始区间,由起点的 endItv
确定;终点要插入的结束区间,由终点的 startItv
确定。
由 startItv
和 endItv
是否相等,决定是否将开始区间的起点/结束区间的终点替换成 A
/ B
。
可以直接将开始区间和结束区间合并,除了以下几种特殊情况:
① 终点的 startItv == -1
,则要在原来的区间数组之前插入新区间。
② 起点的插入区间的索引等于 size
,则要在原来的区间数组之后插入新区间。
③ 起点和终点位于同样的两个区间之间,在这种情况下,起点的 endItv
和终点的 startItv
顺序会相反。即是说,确定的开始区间在结束区间之后。
可以看出,①②的判断条件,本质上也是起点的 endItv
和终点的 startItv
顺序相反。
对①②③,都只需要将新区间插入区间数组的,起点的 endItv
处。
最终,进行插入操作的函数的代码如下:
vector<Interval> insert(vector<Interval>& intervals, Interval newInterval) {
if(intervals.size() == 0){
intervals.push_back(newInterval);
return intervals;
}
int A = newInterval.start, B = newInterval.end;
int startItvA, endItvA;
find(intervals, A, startItvA, endItvA);
int startItvB, endItvB;
find(intervals, B, startItvB, endItvB);
int insertA; // 表示起点要插入的开始区间
int insertB; // 表示终点要插入的结束区间
insertA = endItvA;
insertB = startItvB;
if(insertB < insertA){
intervals.insert(intervals.begin() + insertA, Interval(A, B));
return intervals;
}
if(startItvA != endItvA) intervals[insertA].start = A;
if(startItvB != endItvB) intervals[insertB].end = B;
intervals[insertA].end = intervals[insertB].end;
intervals.erase(intervals.begin() + insertA + 1, intervals.begin() + insertB + 1);
return intervals;
}
提供几组测试数据,方便后来人:
区间数组 1 :[[1,4][6,12][16,25]]
要插入的新区间:
[-7,0]
[-2,14]
[-6,23]
[-5,31]
[2,14]
[7,23]
[7,33]
[3,15]
[7,23]
[13,33]
[30,39]
[5,5]
区间数组 2 :[]
要插入的新区间:
[-7,0]
结语
这道题虽然难度是 Hard
,提交率也是比较低的 29.9%
,但其实,输入的区间数组的有序性使得它的核心算法很简单,只是插入区间需要考虑各种特殊情况,这可能是提交率低的原因。大概是一道假的难题了。