區間覆蓋與合併

問題

最近打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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章