Implement a MyCalendarTwo
class to store your events. A new event can be added if adding the event will not cause a triple booking.
Your class will have one method, book(int start, int end)
. Formally, this represents a booking on the half open interval [start, end)
, the range of real numbers x
such that start <= x < end
.
A triple booking happens when three events have some non-empty intersection (ie., there is some time that is common to all 3 events.)
For each call to the method MyCalendar.book
, return true
if the event can be added to the calendar successfully without causing a triple booking. Otherwise, return false
and do not add the event to the calendar.
Your class will be called like this: MyCalendar cal = new MyCalendar();
MyCalendar.book(start, end)
Example 1:
MyCalendar(); MyCalendar.book(10, 20); // returns true MyCalendar.book(50, 60); // returns true MyCalendar.book(10, 40); // returns true MyCalendar.book(5, 15); // returns false MyCalendar.book(5, 10); // returns true MyCalendar.book(25, 55); // returns true Explanation: The first two events can be booked. The third event can be double booked. The fourth event (5, 15) can't be booked, because it would result in a triple booking. The fifth event (5, 10) can be booked, as it does not use time 10 which is already double booked. The sixth event (25, 55) can be booked, as the time in [25, 40) will be double booked with the third event; the time [40, 50) will be single booked, and the time [50, 55) will be double booked with the second event.
Note:
- The number of calls to
MyCalendar.book
per test case will be at most1000
. - In calls to
MyCalendar.book(start, end)
,start
andend
are integers in the range[0, 10^9]
.
這道題是 My Calendar I 的拓展,之前那道題說是不能有任何的重疊區間,而這道題說最多容忍兩個重疊區域,注意是重疊區域,不是事件。比如事件 A,B,C 互不重疊,但是有一個事件D,和這三個事件都重疊,這樣是可以的,因為重疊的區域最多只有兩個。所以關鍵還是要知道具體的重疊區域,如果兩個事件重疊,那么重疊區域就是它們的交集,求交集的方法是兩個區間的起始時間中的較大值,到結束時間中的較小值。可以用一個 TreeSet 來專門存重疊區間,再用一個 TreeSet 來存完整的區間,那么思路就是,先遍歷專門存重疊區間的 TreeSet,因為能在這里出現的區間,都已經是出現兩次了,如果當前新的區間跟重疊區間有交集的話,說明此時三個事件重疊了,直接返回 false。如果當前區間跟重疊區間沒有交集的話,則再來遍歷完整區間的集合,如果有交集的話,那么應該算出重疊區間並且加入放重疊區間的 TreeSet 中。最后記得將新區間加入完整區間的 TreeSet 中,參見代碼如下:
解法一:
class MyCalendarTwo { public: MyCalendarTwo() {} bool book(int start, int end) { for (auto &a : s2) { if (start >= a.second || end <= a.first) continue; return false; } for (auto &a : s1) { if (start >= a.second || end <= a.first) continue; s2.insert({max(start, a.first), min(end, a.second)}); } s1.insert({start, end}); return true; } private: set<pair<int, int>> s1, s2; };
下面這種方法相當的巧妙,建立一個時間點和次數之間的映射,規定遇到起始時間點,次數加1,遇到結束時間點,次數減1。那么首先更改新的起始時間 start 和結束時間 end 的映射,start 對應值增1,end 對應值減1。然后定義一個變量 cnt,來統計當前的次數。使用 TreeMap 具有自動排序的功能,所以遍歷的時候就是按時間順序的,最先遍歷到的一定是一個起始時間,所以加上其映射值,一定是個正數。如果此時只有一個區間,就是剛加進來的區間的話,那么首先肯定遍歷到 start,那么 cnt 此時加1,然后就會遍歷到 end,那么此時 cnt 減1,最后下來 cnt 為0,沒有重疊。還是用具體數字來說吧,現在假設 TreeMap 中已經加入了一個區間 [3, 5) 了,就有下面的映射:
3 -> 1
5 -> -1
假如此時要加入的區間為 [3, 8) 的話,則先對3和8分別加1減1,此時的映射為:
3 -> 2
5 -> -1
8 -> -1
最先遍歷到3,cnt 為2,沒有超過3,此時有兩個事件有重疊,是允許的。然后遍歷5和8,分別減去1,最終又變成0了,始終 cnt 沒有超過2,所以是符合題意的。如果此時再加入一個新的區間 [1, 4),則先對1和4分別加1減1,那么此時的映射為:
1 -> 1
3 -> 2
4 -> -1
5 -> -1
8 -> -1
先遍歷到1,cnt為1,然后遍歷到3,此時 cnt 為3了,那么就知道有三個事件有重疊區間了,所以這個新區間是不能加入的,需要還原其 start 和 end 做的操作,把 start 的映射值減1,end 的映射值加1,然后返回 false。否則沒有三個事件有共同重疊區間的話,返回 true 即可,參見代碼如下:
解法二:
class MyCalendarTwo { public: MyCalendarTwo() {} bool book(int start, int end) { ++freq[start]; --freq[end]; int cnt = 0; for (auto f : freq) { cnt += f.second; if (cnt == 3) { --freq[start]; ++freq[end]; return false; } } return true; } private: map<int, int> freq; };
Github 同步地址:
https://github.com/grandyang/leetcode/issues/731
類似題目:
參考資料:
https://leetcode.com/problems/my-calendar-ii/
https://leetcode.com/problems/my-calendar-ii/discuss/109550/Simple-AC-by-TreeMap
https://leetcode.com/problems/my-calendar-ii/discuss/109522/Simplified-winner's-solution
https://leetcode.com/problems/my-calendar-ii/discuss/109519/JavaC%2B%2B-Clean-Code-with-Explanation