读完本文,题解几类区间问题。
本文Leetcode题解:
56 合并区间 (Medium)
1288 删除被覆盖的区间 (Medium)
986 区间列表的交集 (Medium)
57 插入区间(Hard)
做区间问题最重要的两点:排序和画图
排序,当然很重要。如果区间的列表是任意散落的,不方便我们统一区间之间的关系,遍历的时候也会带来些许麻烦。试想一下,区间起始一会从1跳到8,又回到4,我们就没法很好处理他们之间的关系。因此我们通常按照起始起点从小到大排序。
画图其实是为了帮助你找到区间与区间的关系。例如重叠时候有几种位置可能性,不重叠又有几种?因此画图找关系。
不多说了,做题。
56 合并区间
也就是说我们需要遍历区间,将重叠的区间合并成新的区间。
由于起点是按照从小到大排序的,我们需要将当前区间的的开头与res内容当中最后的尾部对比。
大家可以动手画画图。
如果当前区间的开头与上一个(res尾部)区间产生了重叠,那么就更新res尾部。
如果没有产生重叠,那就直接让这个区间添加到res当中,继续下一次遍历。
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
if(intervals.size()==0) return {};
//放答案
vector<vector<int>> res;
//排序
sort(intervals.begin(),intervals.end());
res.push_back(intervals[0]);
for(int i = 1;i<intervals.size();i++){
//重叠
if(!(intervals[i][0]>res.back()[1])){
res.back()[1] = max(intervals[i][1],res.back()[1]);
}
else res.push_back(intervals[i]); //不重叠
}
return res;
}
};
1288 删除被覆盖的区间 (Medium)
有了上一题的基础,做这道题就简单了。但是需要注意,当两个区间起始相同时,我们将会出现这种情况。
很显然当开头相同时,应该按照末尾降序的情况排。否则出现右边的情况是,无法将上面的区间有效删除。
class Solution {
public:
int removeCoveredIntervals(vector<vector<int>>& intervals) {
if(intervals.size()==0) return {};
vector<vector<int>> res;
sort(intervals.begin(),intervals.end(),cmp);
res.push_back(intervals[0]);
for(int i=1;i<intervals.size();i++){
if(!(intervals[i][0]>=res.back()[0]&& intervals[i][1]<=res.back()[1])) res.push_back(intervals[i]);
}
return res.size();
}
static bool cmp(const vector<int> &a,const vector<int> &b){
if(a[0]==b[0]) return b[1]<a[1];
else return a[0]<b[0];
}
};
986 区间列表的交集 (Medium)
这道题的改变在于,有两个区间列表来找出他们的重叠部分。
比较需要注意的是,
我B列表中的某个区间可能同时与A区间好几个的区间都产生重叠,因此需要使用两个指针,来控制比较的进度。
class Solution {
public:
vector<vector<int>> intervalIntersection(vector<vector<int>>& A, vector<vector<int>>& B) {
int i=0,j=0;
vector<vector<int>> res;
while(i<A.size()&&j<B.size()){
//相交
if(!(A[i][1]<B[j][0] || A[i][0]>B[j][1])){
vector<int> tmp;
tmp.push_back(max(A[i][0],B[j][0]));
tmp.push_back(min(A[i][1],B[j][1]));
res.push_back(tmp);
}
//指针控制状态
if(B[j][1]<A[i][1]) j++;
else i++;
}
return res;
}
};
57 插入区间(Hard)
对于这个newInterval,将其初始化为我们要比较的标准,使用left和right来说明因为冲突而重构后的范围。遍历区间列表,如果找到与其冲突的区间(可能有好几个都符合冲突情形的区间)就要进行更新操作,当这种冲突状态结束后(想想我们应该怎么在代码中描述?),将最终的更新区间结果插入到结果中。
如果当前遍历的区间不存在什么与比对区间冲突的情况,那就直接天即进去答案。
其实也不算太麻烦,关键在于想清楚这个处理的过程。 我一开始犯的错误在于,找出重叠区域(做标记),更新成新区间。最后在全局遍历的时候再重新填入,其实这样处理很麻烦,也容易出错。
所以这道题解很关键的是,重新生成的新区间应该在什么时候加进去?
我们刚刚讲到了left和right,仔细想想。
1)如果newInterval能够不与列表中的区间产生冲突,即可以直接插入,那么left和right的范围自然是初始的newInterval值。
int left=newInterval[0],right = newInterval[1];
2)如果newInterval在区间内产生了冲突,我们将会在区间冲突结束的地方开始插入新结果。
但是我们还得注意一个细节,如果newInterval直接大于所有列表区间,不能在循环中得到结果。因此我们应该用一个flag来表示,如果flag在区间中改变,表示该newInterval插入在区间内便完成。当flag没有变化,说明循环中没有插入newInterval的动作,直接添加在答案末尾即可。
看代码配合讲解更好体会。
class Solution {
public:
vector<vector<int>> insert(vector<vector<int>>& intervals, vector<int>& newInterval) {
vector<vector<int>> res;
if(intervals.size()==0) {
res.push_back(newInterval);
return res;
}
bool bflag = true;
int left=newInterval[0],right = newInterval[1];
for(int i=0;i<intervals.size();i++){
if(!(intervals[i][1]<left||intervals[i][0]>right)){ //与新区间冲突
left = min(left,intervals[i][0]);
right = max(right,intervals[i][1]);
}
else {
//关键:区间内newInterval(可能没有变化,也可能是和冲突区间合并的新结果)的插入
if(intervals[i][0]>right && bflag){
res.push_back({left,right});
bflag = false;
}
res.push_back(intervals[i]);
}
}
if(bflag) res.push_back({left,right}); //直接加在结尾
return res;
}
};
整体思路:
遍历原数组,如果当前区间和新区间没有交集,则直接存入答案;
如果有交集,便和新区间合并,更新新区间的左右边界;
如果当前区间和新区间无交集且大于新区间,则新区间和后面所有的区间都可存入答案。
作者:wo-shi-ge-hun-zi
链接:https://leetcode-cn.com/problems/insert-interval/solution/cyi-ci-bian-li-zhi-jie-fa-jiao-hao-li-jie-by-wo-sh/