A Range Module is a module that tracks ranges of numbers. Your task is to design and implement the following interfaces in an efficient manner.
addRange(int left, int right)Adds the half-open interval[left, right), tracking every real number in that interval. Adding an interval that partially overlaps with currently tracked numbers should add any numbers in the interval[left, right)that are not already tracked.
queryRange(int left, int right)Returns true if and only if every real number in the interval[left, right)is currently being tracked.
removeRange(int left, int right)Stops tracking every real number currently being tracked in the interval[left, right).
Example 1:
addRange(10, 20): null removeRange(14, 16): null queryRange(10, 14): true (Every number in [10, 14) is being tracked) queryRange(13, 15): false (Numbers like 14, 14.03, 14.17 in [13, 15) are not being tracked) queryRange(16, 17): true (The number 16 in [16, 17) is still being tracked, despite the remove operation)
Note:
- A half open interval
[left, right)denotes all real numbersleft <= x < right. 0 < left < right < 10^9in all calls toaddRange, queryRange, removeRange.- The total number of calls to
addRangein a single test case is at most1000. - The total number of calls to
queryRangein a single test case is at most5000. - The total number of calls to
removeRangein a single test case is at most1000.
這道題讓我們實現一個RangeModule的類,里面有三個功能函數,分別好似插入范圍,查找范圍,刪除范圍,題目中的例子給的也很明確,基本不會引起什么歧義。其實不管范圍也好,區間也好,都是一回事,跟之前的區間的題目Insert Interval和Merge Intervals沒有什么不同。這里的插入范圍函數的實現方法跟之前那道Insert Interval的解法一樣,直接抄過來就好。然后對於查找范圍函數,由於題目中說只要有數字未被包括,就返回false。那么我們反過來想,只有當某個范圍完全覆蓋了這個要查找的范圍才會返回true,所以我們可以遍歷所有的范圍,然后看是否有一個范圍完全覆蓋了要查找的范圍,有的話返回true,循環結束后返回false。最后來看刪除范圍函數,其實現方法跟插入范圍函數很類似,但又有少許不同。首先我們還是新建一個數組res存結果,然后遍歷已有的范圍,如果當前范圍的結束位置小於等於要刪除的范圍的起始位置,由於題目中的范圍定義是左開右閉,那么說明沒有重疊,加入結果res,並且用變量cur自增1來記錄當前位置。如果當前范圍的起始位置大於等於要刪除的范圍的結束位置,說明咩有重疊,加入結果res。否則就是有重疊的情況,這里跟插入范圍有所不同的是,插入范圍只需要加入一個范圍,而刪除范圍操作有可能將原來的大范圍break成為兩個小的范圍,所以我們用一個臨時數組t來存break掉后的小范圍。如果當前范圍的起始位置小於要刪除的范圍的起始位置left,說明此時一定有一段范圍留下來了,即從當前范圍的起始位置到要刪除的范圍的起始位置left,將這段范圍加入臨時數組t,同理,如果當前范圍的結束位置大於要刪除的范圍的結束位置right,將這段范圍加入臨時數組t。最后將數組t加入結果res中的cur位置即可,參見代碼如下:
解法一:
class RangeModule { public: RangeModule() {} void addRange(int left, int right) { vector<pair<int, int>> res; int n = v.size(), cur = 0; for (int i = 0; i < n; ++i) { if (v[i].second < left) { res.push_back(v[i]); ++cur; } else if (v[i].first > right) { res.push_back(v[i]); } else { left = min(left, v[i].first); right = max(right, v[i].second); } } res.insert(res.begin() + cur, {left, right}); v = res; } bool queryRange(int left, int right) { for (auto a : v) { if (a.first <= left && a.second >= right) return true; } return false; } void removeRange(int left, int right) { vector<pair<int, int>> res, t; int n = v.size(), cur = 0; for (int i = 0; i < n; ++i) { if (v[i].second <= left) { res.push_back(v[i]); ++cur; } else if (v[i].first >= right) { res.push_back(v[i]); } else { if (v[i].first < left) { t.push_back({v[i].first, left}); } if (v[i].second > right) { t.push_back({right, v[i].second}); } } } res.insert(res.begin() + cur, t.begin(), t.end()); v = res; } private: vector<pair<int, int>> v; };
下面來看一種優化了時間復雜度的解法,我們使用TreeMap來建立范圍的起始位置和結束位置之間的映射,利用了TreeMap的自動排序功能,其會根據起始位置從小到大進行排序。既然是有序的,我們就可以利用二分法來快速進行查找了。
在加入范圍函數中,我們首先用upper_bound函數來查找第一個大於left的位置,標記為l,再用upper_bound函數來查找第一個大於right的位置,標記為r。我們其實是想找第一個不大於left和right的位置的,由於C++沒有floorKey這個函數,所以我們只能用upper_bound找大於left和right的位置,然后再往前移一個。如果l不是TreeMap中的第一個位置,且前面一個范圍的結束位置小於left,說明和前一個范圍沒有交集,那么還是回到當前這個范圍吧。如果此時l和r指向同一個位置,說明當前要加入的范圍沒有跟其他任何一個范圍有交集,所以我們直接返回即可,不需要其他任何操作。否則的話我們將left和l指向范圍的起始位置中的較小值賦給i,將right和r指向的前一個位置的結束位置的較大值賦給j,然后將l和r之間的范圍都刪除掉(注意這里r自增了1,是因為之前先自減了1),然后將i和j返回即可。返回后我們建立起這個映射即可。
在查找范圍函數中,我們先用upper_bound找出第一個大於left位置的范圍it,然后看如果it不是第一個范圍,並且如果其前面的一個范圍的結束位置大於等於right,說明已經完全包括這個要查找的范圍,因為前一個范圍的起始位置小於left,且結束位置大於等於right,直接返回true。
在刪除范圍函數中,查找重疊范圍的方法跟加入范圍函數中的操作一樣,所以抽出來放到了find函數中,由於刪除的范圍有可能完全覆蓋了原有范圍,也有可能只是部分覆蓋,將一個大的范圍拆成了一個或者兩個范圍。所以我們判斷,如果left大於覆蓋范圍的起始位置,那么將這段建立映射,同理,如果覆蓋范圍的結束位置大於right,同樣建立這段的映射,參見代碼如下:
解法二:
class RangeModule { public: RangeModule() {} void addRange(int left, int right) { auto x = find(left, right); m[x.first] = x.second; } bool queryRange(int left, int right) { auto it = m.upper_bound(left); return it != m.begin() && (--it)->second >= right; } void removeRange(int left, int right) { auto x = find(left, right); if (left > x.first) m[x.first] = left; if (x.second > right) m[right] = x.second; } private: map<int, int> m; pair<int, int> find(int left, int right) { auto l = m.upper_bound(left), r = m.upper_bound(right); if (l != m.begin() && (--l)->second < left) ++l; if (l == r) return {left, right}; int i = min(left, l->first), j = max(right, (--r)->second); m.erase(l, ++r); return {i, j}; } };
類似題目:
Data Stream as Disjoint Intervals
參考資料:
https://leetcode.com/problems/range-module/discuss/108914/c++-code
