题目描述
Given a set of intervals, for each of the interval i, check if there exists an interval j whose start point is bigger than or equal to the end point of the interval i, which can be called that j is on the “right” of i.
For any interval i, you need to store the minimum interval j’s index, which means that the interval j has the minimum start point to build the “right” relationship for interval i. If the interval j doesn’t exist, store -1 for the interval i. Finally, you need output the stored value of each interval as an array.
Note:
You may assume the interval’s end point is always bigger than its start point.
You may assume none of these intervals have the same start point.
Example 1:
Input: [ [1,2] ]
Output: [-1]
Explanation: There is only one interval in the collection, so it outputs -1.
Example 2:
Input: [ [3,4], [2,3], [1,2] ]
Output: [-1, 0, 1]
Explanation: There is no satisfied “right” interval for [3,4].
For [2,3], the interval [3,4] has minimum-“right” start point;
For [1,2], the interval [2,3] has minimum-“right” start point.
Example 3:
Input: [ [1,4], [2,3], [3,4] ]
Output: [-1, 2, -1]
Explanation: There is no satisfied “right” interval for [1,4] and [3,4].
For [2,3], the interval [3,4] has minimum-“right” start point.
解题思路
这道题初读有些绕口,其实简单点说,有一个由“间隔”构成的数组(所谓的“间隔”,就是一对升序排列的数),对于每一个间隔,找到另一个间隔,它满足这样的关系:该间隔的左侧结点大于先前间隔的右侧结点,我们称这样的间隔为右侧间隔(right interval),这样的右侧间隔可能有多个,返回其中左侧值最小的间隔的下标,若不存在这样的右侧间隔,则返回-1.
很容易想到排序。这也是遇到此类问题的一个正常思路,我们希望按某个序排好的数组具备某些满足题目要求的性质,从而能够从这个数组中得到最终解。一个简单的考虑:按start(左侧值)或end(右侧值)排序(先不考虑下标问题,下标可以以某种方式记录)。我们分别思考这两种排序对应的情况:
1 start排序。
start方式的排序按照start从小到大重新排序序列。我们思考这样做得到的结果:我们知道,我们要求end对应的第一个大于end的左侧start,那么这样的排序显然合理,出现在右侧的大于该end的start一定是要求的第一个start,我们只需遍历新数组即可。
2 end排序。
end方式的排序按照end从小到大排序序列。对于数组中的每一个序列,我们能够知道的只是下个end是大于当前间隔的end的,但是我们无从得知start与end的关系,这样得到的关系更像是一个左侧关系(find left interval),该间隔右侧的第一个end一定是大于左侧start的第一个end,这与题目要求并不符合。
实现思路:
我们需要一个新的数据结构保存interval和他们的初始位置下标,生成一个新数组并进行排序,关于该思路的优化操作会在第二种实现中说明。
/**
* Definition for an interval.
* struct Interval {
* int start;
* int end;
* Interval() : start(0), end(0) {}
* Interval(int s, int e) : start(s), end(e) {}
* };
*/
struct Wrapper{
Interval inter;
int pos;
Wrapper(Interval i,int p):inter(i),pos(p){};
};
class Solution {
public:
int findPos(vector<Wrapper>& mWrapper,vector<Wrapper>::iterator fIter) {
if(fIter+1==mWrapper.end()) return -1;
auto left = fIter+1;
auto right = mWrapper.end()-1;
while(left<right) {
auto mid = left+(right-left)/2;
if(mid->inter.start==fIter->inter.end) return mid->pos;
if(mid->inter.start>fIter->inter.end) right = mid;
else left = mid+1;
}
if(left->inter.start>=fIter->inter.end) return left->pos;
return -1;
}
vector<int> findRightInterval(vector<Interval>& intervals) {
vector<Wrapper> mWrapper;
for(int i = 0;i<intervals.size();++i) {
mWrapper.emplace_back(Wrapper(intervals[i],i));
}
auto cmp = [](const Wrapper&lhs, const Wrapper&rhs)->int{
return lhs.inter.start<rhs.inter.start;
};
sort(mWrapper.begin(),mWrapper.end(),cmp);
vector<int> retVec(intervals.size(),-1);
auto iter = mWrapper.begin();
for(;iter!=mWrapper.end();iter++) {
retVec[iter->pos] = findPos(mWrapper,iter);
}
return retVec;
}
};
从最后的统计结果来看,这种解法的速度超过了91%的cpp提交,从速度的角度考虑无疑已经达到了最优,但是一个令人烦躁的问题就在于,我们甚至需要对自定义的数据结构定义一个lambda(当然也可以是其它可调用对象)来完成排序工作,此外,我们仍需自己实现二分查找函数,来进行查找加速(否则,很容易出现超时情况)。这种写法无疑是我们必须掌握的解法。然而,在思想确定的情况下,多利用stl库可能更有利于在面试过程中快速拿下本题。下面将要介绍的解法利用了map数据结构来解这道题,我们知道,map中的数据是按照key进行字典排序的,也就是说,我们只需要遍历一遍该数组,就可以得到一个按start排序的map,之后,我们只需要在map中进行二分查找(利用stl lower_bound)就可以找到答案。这种解法,无疑是要比上述解法慢的,map的插入时间,整体二分查找的耗时,都是时间消耗所在,但是这种解法胜在写法简单,可以快速AC。
vector<int> findRightInterval(vector<Interval>& intervals) {
map<int,int> iMap;
vector<int> retVec;
for(int i = 0;i<intervals.size();++i) {
iMap[intervals[i].start] = i;
}
for(auto&val:intervals) {
auto iter = iMap.lower_bound(val.end);
if(iter!=iMap.end()) {
retVec.push_back(iter->second);
}else {
retVec.push_back(-1);
}
}
return retVec;
}