问题
最近打google的apactest,遇到一个经典的(但我不熟的)问题——给你一堆整数区间(比如[1, 3], [2, 6], [8, 10]),问它们合并后是怎样的?
比如上述三个区间合并后就变成:[1, 6], [8, 10]。
这个问题在leetcode上的难度评级是Hard,简直亮瞎啊,其实并不难呀:https://leetcode.com/problems/merge-intervals/
思路1
如果,区间的端点的范围很小,比如都在[0, 9999],那很简单地开个bool数组,然后对于每个区间覆盖到的点都赋值为true,最后的区间就是那些连续一片一片的true,比如上述的三个区间的话,bool数组将是这样的:0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0,…把这些连续的true的端点提取出来就是合并后的区间啦!
这样做的限制是:
1. 区间的范围得较小,不然没法开数组;
2. 区间的平均跨度不是很大或区间的数量不多。
为什么会有2这条限制呢?先算一下这样做的复杂度:
假设区间的平均跨度为L,有N个区间,所有区间端点的范围的长度为M,那么时间复杂度就是O(L*N + M)。如果L很大并且N很大,跟M一个数量级的话,那么这个算法将会超级慢的!
思路2
想法是很容易出来的,我们先按区间的左端点排序(不要问为什么,因为直觉,不排序的话,基本没法做啊)。
然后维护一个left和right值,表示当前已有的连续区间的左端点和右端点,它们的初始值自然就是第一个区间的端点值啦,还是拿上面的那三个区间作为例子。
第一步:[1, 3],则left = 1, right = 3
第二步:[2, 6],发现2还是小于right的,说明当前这个区间和前面的还是“连在一起的”,所以left值不用变,只需要更新right值就好了,right明显等于max(3, 6) = 6。
第三步,[8, 10],发现8比right大,所以当前这个区间和前面的区间是“断开”的,前面的区间独立作为一个,然后left和right都从这个区间开始,嗯。
最后的结果自然是:[1, 6],[8, 10]啦~
分析一下时间复杂度,排序的时候是O(NlogN),然后线性扫一遍是O(N),所以总的时间复杂度是O(NlogN),跟区间的跨度没关系,跟区间端点的覆盖范围也没一毛钱关系!
只跟区间的个数有关系,而且还是很可以接受的一个复杂度(毕竟log N跟常数的区别不是很大)。
代码
只给出第二种思路的代码,第一种太过简单,不用了吧:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef vector<pair<int, int> > RangeList;
RangeList cover(const RangeList& intervals) {
int left = intervals[0].first, right = intervals[0].second;
RangeList result;
for (int i = 1; i < intervals.size(); ++i) {
// 前面自成一个区间,那么就此分开
if (intervals[i].first > right) {
result.push_back(make_pair(left, right));
left = intervals[i].first;
right = intervals[i].second;
} else if (intervals[i].second > right) {
right = intervals[i].second;
}
}
result.push_back(make_pair(left, right));
return result;
}
void display(const RangeList& intervals) {
for (int i = 0; i < intervals.size(); ++i)
cout << intervals[i].first << ' ' << intervals[i].second << endl;
cout << endl;
}
int main() {
RangeList intervals;
int n, start, end;
cin >> n;
for (int i = 0; i < n; ++i) {
cin >> start >> end;
intervals.push_back(make_pair(start, end));
}
sort(intervals.begin(), intervals.end());
display(intervals);
RangeList result = cover(intervals);
display(result);
return 0;
}