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]
.
一道看起来不难的题目,却做了很久才AC。主要原因还是思路不清晰导致的。
思路一:直接求解,简单说就是蛮力算法O(N),依次遍历过去,判断是否合并插入。
我自己写了一个直接求解的代码,结果超时了。但是后面看答案,发现很多人都是用的蛮力,却没有超时。我想,可能是跟我用的erase跟insert有关系。
先上大神思路清晰的代码吧:
用新建的vector来存储答案,遇到与新间隔无交叠的就压入,遇到有交叠的就更新新间隔的范围。这样免去了erase和insert的麻烦。
最核心的,通过改变newInterval的范围来解决被覆盖的间隔!!
vector<Interval> insert(vector<Interval>& intervals, Interval newInterval) { vector<Interval> ret; auto it = intervals.begin(); for(; it!=intervals.end(); ++it){ if(newInterval.end < (*it).start) //all intervals after will not overlap with the newInterval break; else if(newInterval.start > (*it).end) //*it will not overlap with the newInterval ret.push_back(*it); else{ //update newInterval bacause *it overlap with the newInterval newInterval.start = min(newInterval.start, (*it).start); newInterval.end = max(newInterval.end, (*it).end); } } // don't forget the rest of the intervals and the newInterval ret.push_back(newInterval); for(; it!=intervals.end(); ++it) ret.push_back(*it); return ret; }
想了想,我那又长又臭的代码还是不放上来了...
思路二:二分搜索。我想,既然我自己写得O(n)的代码超时了,那只能想复杂度更少一些的代码了。于是就想用二分搜索来定位新间隔的起始和结束点应该在原间隔的哪个位置。
以上图为例,黑色的是初始间隔,脚标与间隔在vector中的关系是2n, 2n + 1。那么对于newInterval的start和end都有四种可能:
①比最小值还小,返回-1 -1
②比最大值还大,如图就是比16大,返回10 10
③落在某个范围里,如8, 返回 6 7。注意,边界值如1,2,3,5之类的,是算在范围里的。
④落在某个间隙里,如11, 返回 7 8
找到落在的范围后直接把覆盖的范围删除。在交叠的地方,我处理的还是很乱。
这份代码虽然AC了,但我自己并不满意。二分查找很混乱,交叠处理也很混乱,各种混乱!有时间再理一下吧。
注意: 判断奇数偶数时 ((r & 0x01) ==1) 里面的&一定要括起来!
vector<Interval> insert2(vector<Interval>& intervals, Interval newInterval) { if(intervals.empty()) { intervals.push_back(newInterval); return intervals; } Interval startPos = DividedSearch(intervals, newInterval.start); Interval endPos = DividedSearch(intervals, newInterval.end); if(endPos.end == -1) //新间隔比所有的已有间隔都小 插到最前面 { intervals.insert(intervals.begin(), newInterval); } else if(startPos.start == 2 * intervals.size()) //新间隔比所有的已有间隔都大 插到最后面 { intervals.push_back(newInterval); } else { if(startPos.start == endPos.start && startPos.start & 0x01 == 1) //start与end都落在同一个间隙 { intervals.insert(intervals.begin() + startPos.start / 2 + 1, newInterval); //在该间隙插入 } else if(startPos.start == endPos.start && (startPos.start & 0x01) == 0) //start与end都落在同一个范围 什么都不做 原范围包含了新范围 { } else { //把覆盖的新范围幅值到被覆盖的第一个范围上 intervals[startPos.end / 2].start = (newInterval.start < intervals[startPos.end / 2].start) ? newInterval.start : intervals[startPos.end / 2].start; if(endPos.start == 2 * intervals.size()) { intervals[startPos.end / 2].end = newInterval.end; intervals.erase(intervals.begin() + startPos.end / 2 + 1, intervals.end()); } else { intervals[startPos.end / 2].end = ((endPos.start & 0x01) == 1) ? newInterval.end : intervals[endPos.start / 2].end; //擦除被覆盖的范围 intervals.erase(intervals.begin() + startPos.end / 2 + 1, intervals.begin() + endPos.start / 2 + 1); } } } return intervals; } //二分查找定位新间隔的最大值和最小值落在哪个范围 注意数字可能在两个范围的缝隙中 Interval DividedSearch(vector<Interval> intervals, int num) { if(num < intervals[0].start) return Interval(-1, -1); //比最小值小 if(num > intervals.back().end) return Interval(2 * intervals.size(), 2 * intervals.size()); //比最大值大 int l = 0, r = 2 * intervals.size() - 1; Interval range(l, r); while(l < r - 1) { int m = l + (r - l) / 2; int mnum = (m & 0x01 == 1) ? intervals[m / 2].end : intervals[m / 2].start; //m是奇数对应end 是偶数对应start int lnum = (l & 0x01 == 1) ? intervals[l / 2].end : intervals[l / 2].start; int rnum = (r & 0x01 == 1) ? intervals[r / 2].end : intervals[r / 2].start; if(lnum <= num && num <= mnum) { r = m; } else if(mnum <= num && num <= rnum) { l = m; } else { break; } } int lnum = (l & 0x01 == 1) ? intervals[l / 2].end : intervals[l / 2].start; int rnum = (r & 0x01 == 1) ? intervals[r / 2].end : intervals[r / 2].start; if(num == lnum && ((l & 0x01) == 1)) { r = l; l = r - 1; } else if(num == rnum && ((r & 0x0001) == 0)) //注意 (r & 0x0001) 一定要括起来 { l = r; r = l + 1; } return Interval(l, r); }
还有,这道题的时间分布很奇怪,最快的居然是python。