一、删除覆盖区间
题目:
给一个区间数组,删除最小个数的、被覆盖的区间,返回最终删除覆盖区间后剩余的区间个数。对于区间[a,b)和[c,d)当且仅当c>=a且d<=b的时候,a-b完全覆盖c-d
区间算法解决的重点:
1、升序排列,定出区间之间左端点的关系,只需使用右端点即可得到区间关系,简化操作
2、区间关系有三种:
- 完全覆盖,即某一区间左右端点都被包含在内
- 部分覆盖,有一部分伸出来,此时可以扩展现已覆盖的总区间的左右边界
- 不相交
解题思路:
!注意:在这个题目中,由于升序排列区间之后,能产生覆盖只可能在相邻的区间中产生覆盖,因此,只需要用left和right记录此时最邻近的、已覆盖的最大区间,由此判断下一个区间是否会产生完全覆盖
小坑记录:
bool比较函数传入sort方法的时候,请用static定义
代码如下:
class Solution {
public:
static bool compare(const vector<int>&a,const vector<int>&b){
if(a[0]<b[0]) return true;
else if(a[0]==b[0] && a[1]<b[1]) return true;
else return false;
}
int removeCoveredIntervals(vector<vector<int>>& intervals) {
//升序排列
sort(intervals.begin(),intervals.end(),compare);
//只有当完全被覆盖的时候删除
//只需要记录left和right即当前已存在的最大区间的左右边界
//由于本身就是升序排列的,那么,当遇到断开的不交集新区间,重新赋值left、right即可,因为区间升序排列,只可能在邻近区间操作
int left=intervals[0][0],right=intervals[0][1];
int count=intervals.size();
for(int i=1;i<intervals.size();i++){
cout<<left<<" "<<right<<endl;
//区间关系分为三种情况:
//1、完全覆盖
//2、有交集,合并为大区间
//3、无交集,更新为新区间
if(intervals[i][0]>=left && intervals[i][1]<=right){
count--;
}
else if(right>=intervals[i][0] && right<=intervals[i][1]){
//注意,这里之所以不考虑left的关系,是因为left必然小于等于新遍历到的区间->这就是升序排列控制的性质
right=intervals[i][1];
}
else{
left=intervals[i][0];
right=intervals[i][1];
}
}
return count;
}
};
二、合并区间
题目:将区间合并为不相交区间列表
按照上面所说的区间关系,记录left和right,不断更新
在断开区间的时候或者在遍历末尾,将新纪录的区间放入
class Solution {
public:
static bool compare(const vector<int> & a,const vector<int> &b){
if(a[0]<b[0]) return true;
else if(a[0]==b[0] && a[1]<b[1]) return true;
else return false;
}
vector<vector<int>> merge(vector<vector<int>>& intervals) {
vector<vector<int>> mergeInterval;
//排序
sort(intervals.begin(),intervals.end(),compare);
int left=intervals[0][0],right=intervals[0][1];
for(int i=1;i<intervals.size();i++){
//部分覆盖
if(right>=intervals[i][0] && right<=intervals[i][1]) right=intervals[i][1];
else if(right<intervals[i][0]){//没有交集
//刷新,存入区间
vector<int> qujian;
qujian.push_back(left);
qujian.push_back(right);
mergeInterval.push_back(qujian);
//更新left 和 right
left=intervals[i][0];
right=intervals[i][1];
}
}
//最后,记录下来的合并区间,最新的,还没来得及放入到数组中,最后继续放入
vector<int> qujian;
qujian.push_back(left);
qujian.push_back(right);
mergeInterval.push_back(qujian);
return mergeInterval;
}
};
三、区间之间的交集
题目:两个不相交的、已升序排列的区间列表,求出两个区间列表之间存在的相交区间集合(集合左闭右闭)
解题思路:
1、双指针法,根据升序、不相交的特性,每次对比,移动具有更小右值的区间列表的指针【这样的移动可能会带来又一次相交】
2、计算相交区间
上面讨论了区间有三种宏观上的关系,除了不相交以外,都可以归为相交情况。但是相交区间要求具体的相交范围,可以总结为以下4种可能性
相交区间的关系:
对于区间[a1,b1]和[a2,b2]
- 若a1>=a2并且b1<=b2,则相交区间为[a1,b1]
- 若a2>=a1并且b2<=b1,则相交区间为[a2,b2]
- 若a2>=a1并且b1<=b2,则相交区间为[a2,b1]
- 若a1>=a2并且b2<=b1,则相交区间为[a1,b2]
总结下来如下图所示:
由上述规律总结,对于两个区间的交集部分,设为[c1,c2]
c1=max(a1,a2). c2=min(b1,b2)
代码如下:
class Solution {
public:
vector<vector<int>> intervalIntersection(vector<vector<int>>& firstList, vector<vector<int>>& secondList) {
vector<vector<int>> res;
int len1=firstList.size(),len2=secondList.size();
//特殊情况,两个区间列表一样没有交集
if(!len1 || !len2 || firstList[len1-1][1]<secondList[0][0] || secondList[len2-1][1]<firstList[0][0]) return res;
//双指针法
int j=0,i=0;
while(i<len1 && j<len2){
//如果不是完全不相交
if(!(firstList[i][0]>secondList[j][1] || firstList[i][1]<secondList[j][0])) {
vector<int> seq;
//相交的区间有四种情况,总结来就是取边界
int left=max(firstList[i][0],secondList[j][0]);
int right=min(firstList[i][1],secondList[j][1]);
seq.push_back(left);
seq.push_back(right);
res.push_back(seq);
}
//注意:每个区间列表中都是升序、不相交的;所以下一个区间的左值一定大于上一个区间的有值
//所以还可能继续被交的【即右值范围更大的留着】,另一个的列表指针移动
if(firstList[i][1]<secondList[j][1]){
i++;
}
else j++;
}
return res;
}
};