My Calendar I
MedPrompt
You are implementing a program to use as your calendar. We can add a new event if adding the event will not cause a double booking.
A double booking happens when two events have some non-empty intersection (i.e., some moment is common to both events.).
The event can be represented as a pair of integers startTime and endTime that represents a booking on the half-open interval [startTime, endTime), the range of real numbers x such that startTime <= x < endTime.
Implement the MyCalendar class:
MyCalendar()Initializes the calendar object.boolean book(int startTime, int endTime)Returnstrueif the event can be added to the calendar successfully without causing a double booking. Otherwise, returnfalseand do not add the event to the calendar.
Example 1:
Input
["MyCalendar", "book", "book", "book"]
[[], [10, 20], [15, 25], [20, 30]]
Output
[null, true, false, true]
Explanation
MyCalendar myCalendar = new MyCalendar();
myCalendar.book(10, 20); // return True
myCalendar.book(15, 25); // return False, It can not be booked because time 15 is already booked by another event.
myCalendar.book(20, 30); // return True, The event can be booked, as the first event takes every time less than 20, but not including 20.
Constraints:
0 <= start < end <= 109- At most
1000calls will be made tobook.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach uses a simple list to store all the booked events. For each new booking request, it iterates through the entire list of existing events to check for any overlaps. While straightforward, its performance degrades as the number of events increases.
Algorithm
- Initialize a list,
bookings, to store the event intervals[start, end). - When
book(start, end)is called, iterate through every eventbalready in thebookingslist. - For each event
b = [s, e), check if the new event[start, end)overlaps with it. The condition for an overlap isstart < eands < end. A more concise way to check this isMath.max(start, s) < Math.min(end, e). - If an overlap is found with any existing event, the new event cannot be booked. Return
falseimmediately. - If the loop completes without finding any overlaps, the booking is valid. Add the new event
[start, end)to thebookingslist and returntrue.
Walkthrough
We maintain a simple ArrayList to store the event intervals. Each event is represented as an array of two integers, [startTime, endTime).
When the book(start, end) method is called, we perform a linear scan through our list of already booked events. For each existing event [s, e), we check if it has a non-empty intersection with the new event [start, end). Two half-open intervals [s1, e1) and [s2, e2) overlap if and only if s1 < e2 and s2 < e1.
If an overlap is detected with any event in the list, we know it's a double booking. We immediately return false and do not add the new event to the calendar. If we successfully iterate through the entire list without finding any conflicts, it signifies that the time slot is free. We then add the new event [start, end) to our list and return true.
import java.util.ArrayList;import java.util.List; class MyCalendar { private List<int[]> bookings; public MyCalendar() { bookings = new ArrayList<>(); } public boolean book(int start, int end) { for (int[] b : bookings) { // Check for overlap: max(start_A, start_B) < min(end_A, end_B) if (Math.max(b[0], start) < Math.min(b[1], end)) { return false; } } bookings.add(new int[]{start, end}); return true; }}Complexity
Time
O(N) per `book` call. For `k` existing bookings, we iterate through all `k` of them. Since there can be up to `N` calls, the `k`-th call can take O(k) time. The total time for `N` calls is O(N^2).
Space
O(N), where N is the number of successful bookings. We need to store each booked event.
Trade-offs
Pros
Simple to understand and implement.
Requires minimal code and standard data structures.
Cons
Inefficient for a large number of bookings as the time for each
bookoperation grows linearly with the number of events already in the calendar.
This optimized approach leverages a balanced binary search tree (specifically, a TreeMap in Java) to maintain the events in sorted order. By keeping the events sorted by their start times, we can check for potential overlaps in logarithmic time instead of linear time, leading to a significant performance improvement.
Algorithm
- Use a
TreeMapto store events, mapping start times (keys) to end times (values). TheTreeMapkeeps events sorted by their start times. - When
book(start, end)is called, we need to check for overlaps with potential neighbors. - An overlap can only occur with an event
[s, e)wheres < endande > start. - We can efficiently check this by finding the event
[s_p, e_p)with the largest start times_pthat is still less than our new event'sendtime. This is done usingcalendar.lowerKey(end). - If such an event
[s_p, e_p)exists, we check if it overlaps with[start, end). The overlap condition simplifies to checking ife_p > start. - If
e_p > start, there is an overlap, so we returnfalse. - If no such conflicting event is found, the booking is valid. We insert the new event
(start, end)into theTreeMapand returntrue.
Walkthrough
Instead of a list, we use a TreeMap where keys are the event start times and values are the end times. This structure automatically maintains the events sorted by their start times, which is key to the optimization.
When a new event [start, end) is requested, we don't need to check every single past event. Since the existing events are sorted and non-overlapping, a new event can only conflict with its immediate neighbors in the time line. More specifically, any potential conflict must involve an existing event [s, e) that starts before the new event ends (s < end) and ends after the new event starts (e > start).
A very elegant way to check this is to find the single most likely candidate for an overlap: the event that starts latest but still before the new event's end time. We can find its start time s_p using TreeMap.lowerKey(end). If this event [s_p, e_p) exists, we only need to check if it extends into our new event's time, i.e., if e_p > start. If this condition is met, we have a double booking. Otherwise, no event can possibly overlap.
If no conflict is found, we add the new event to the TreeMap. The search (lowerKey) and insertion (put) operations on a TreeMap take logarithmic time.
import java.util.TreeMap; class MyCalendar { private TreeMap<Integer, Integer> calendar; public MyCalendar() { calendar = new TreeMap<>(); } public boolean book(int start, int end) { // Find the latest booking that starts before the new event ends. Integer prevStart = calendar.lowerKey(end); // If such a booking exists, check if it overlaps with the new event. // Overlap occurs if the previous event's end time is after the new event's start time. if (prevStart != null && calendar.get(prevStart) > start) { return false; } // If no overlap, add the new event and return true. calendar.put(start, end); return true; }}Complexity
Time
O(log N) per `book` call. `TreeMap` operations like `lowerKey` and `put` take O(log k) time, where `k` is the current number of events. The total time for `N` calls is O(N log N).
Space
O(N), where N is the number of successful bookings. The `TreeMap` stores one entry per event.
Trade-offs
Pros
Highly efficient with logarithmic time complexity per booking.
Scales very well as the number of events in the calendar grows.
Cons
More complex to reason about the logic compared to the brute-force approach.
Has a slightly higher constant factor overhead due to the tree data structure.
Solutions
Solution
import java.util.Map ; import java.util.TreeMap ; class MyCalendar { private final TreeMap < Integer , Integer > tm = new TreeMap <>(); public MyCalendar () { } public boolean book ( int start , int end ) { Map . Entry < Integer , Integer > ent = tm . floorEntry ( start ); if ( ent != null && ent . getValue () > start ) { return false ; } ent = tm . ceilingEntry ( start ); if ( ent != null && ent . getKey () < end ) { return false ; } tm . put ( start , end ); return true ; } } /** * Your MyCalendar object will be instantiated and called as such: MyCalendar * obj = new MyCalendar(); boolean param_1 = obj.book(start,end); */var MyCalendar = function () { this . calendar = []; }; /** * @param {number} start * @param {number} end * @return {boolean} */ MyCalendar . prototype . book = function ( start , end ) { for ( const item of this . calendar ) { if ( end <= item [ 0 ] || item [ 1 ] <= start ) { continue ; } return false ; } this . calendar . push ([ start , end ]); return true ; }; /** * Your MyCalendar object will be instantiated and called as such: * var obj = new MyCalendar() * var param_1 = obj.book(start,end) */class MyCalendar { public: map < int , int > m ; MyCalendar () { } bool book ( int start , int end ) { ++ m [ start ]; -- m [ end ]; int s = 0 ; for ( auto & [ k , v ] : m ) { s += v ; if ( s > 1 ) { -- m [ start ]; ++ m [ end ]; return false ; } } return true ; } }; /** * Your MyCalendar object will be instantiated and called as such: * MyCalendar* obj = new MyCalendar(); * bool param_1 = obj->book(start,end); */''' >>> sd SortedDict({'a': 111, 'b': 222, 'c': 333}) >>> sd.keys() SortedKeysView(SortedDict({'a': 111, 'b': 222, 'c': 333})) >>> sd.values() SortedValuesView(SortedDict({'a': 111, 'b': 222, 'c': 333})) >>> sd.items() SortedItemsView(SortedDict({'a': 111, 'b': 222, 'c': 333})) >>> >>> >>> sd.keys()[1] 'b' >>> sd.values()[1] 222 >>> sd.append({'aaa':111}) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'SortedDict' object has no attribute 'append' >>> >>> sd.add({'aaa':111}) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'SortedDict' object has no attribute 'add' ''' from sortedcontainers import SortedDict class MyCalendar : def __init__ ( self ): self . sd = SortedDict () def book ( self , start : int , end : int ) -> bool : # bisect_left will not work, due to duplicates # eg: [[],[10,20],[15,25],[20,30]] idx = self . sd . bisect_right ( start ) # self.sd.keys()[idx] ==> end time # self.sd.values()[idx] ==> it's start time if idx < len ( self . sd ) and end > self . sd . values ()[ idx ]: return False self . sd [ end ] = start return True # Your MyCalendar object will be instantiated and called as such: # obj = MyCalendar() # param_1 = obj.book(start,end) ############ class Node ( object ): # double linked list, full scan def __init__ ( self , s , e ): self . s = s self . e = e self . left = None self . right = None class MyCalendar ( object ): def __init__ ( self ): self . root = None def book_helper ( self , s , e , node ): if node . e <= s : if node . right : return self . book_helper ( s , e , node . right ) else : node . right = Node ( s , e ) return True elif node . s >= e : if node . left : return self . book_helper ( s , e , node . left ) else : node . left = Node ( s , e ) return True else : return False def book ( self , start , end ): """ :type start: int :type end: int :rtype: bool """ if not self . root : self . root = Node ( start , end ) return True else : return self . book_helper ( start , end , self . root ) # Your MyCalendar object will be instantiated and called as such: # obj = MyCalendar() # param_1 = obj.book(start,end)Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.