問題
最近打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;
}