ARTICLES
731 My Calendar II
By Frankie Liu
Acknowledgement
Taken from fun4leet’s My Calendar II wonderful article
tl;dr
A max segment tree, with an incremental update function is sufficient for this
problem. Max is used because such tree allows quick query of the max number
of bookings given a range. Incremental update is useful because each time
book is called, one needs to increment the bookings in a particular range.
Data structure
4 things: a range \([l,r]\), data, lazy flag
Implementation
class segment_node:
def __init__(self):
self.range = [None,None]
self.lazy = None
# normal tree stuff below
self.val = None
self.left = None
self.right = None
API
query(arange)
- check if query range is valid/invalid
- normalize: deal with lazy propagation
- return value if covers current segment
- recurse on left and right subtrees
- combine the left and right results
def query(root, arange):
# take care of edge cases
if root is None:
return 0
i,j = arange
si, sj = root.range
if i>j or j<si or i>sj:
return 0
# normal case
normalize(root) # deal with lazy
if i<=si and sj<=j:
return root.val
return fcombine(query(root.left, arange), query(root,right, arange))
update(arange,val)
- check if arange is valid/invalid
- normalize
- if arange covers current segment, update segment, mark children as lazy
- recurse to left and right subtrees with the same arange
- propagate results from left and right subtrees back to parent node
def update(root, arange, val):
if root is None:
return
i,j = arange
si,sj = root.range
if i>j or j<si or i>sj:
return
normalize(root)
if i <= si and sj <= j:
root.lazy = val
normalize(root)
return
update(root.left, arange, val)
update(root.right, arange, val)
root.val = fcombine(root.left.val, root.right.val)
normalize
- if lazy is not None, then update the node’s value, push laziness to children
def normalize(root):
if root is None:
return
if root.lazy is not None:
root.val = fupdate(root.val, root.lazy)
si,sj = root.range
if si < sj:
if root.left is None or root.right is None:
mid = (si + sj) // 2
root.left = segment_node((si,mid), root.val)
root.right = segment_node((mid+1,sj), root.val)
elif root.lazy is not None:
root.left.lazy = fupdate(root.left.lazy, root.lazy)
root.right.lazy = fupdate(root.right.lazy, root.lazy)
root.lazy = None
For the problem
initialization
Root node contains the whole range (0, 1e9) with everything initialized to 0.
root = segment_node((0,1000000000),0)
book(arange)
- if \(k>=2\) in range, then adding this booking will result in a triple booking, function returns false
- else update range and return true
def book(arange):
k = query(root, arange)
if k >= 2:
return False
update(root, arange, 1)
return True
Complexity
In book, there is one query and one update, both of which take \(\log(d)\) where
\(d\) is the max range. For \(n\) calls to book, \(O(n \log d)\).
In the worst case, each call to book generates a completely separate range.
Assuming this reaches down to leaf nodes, then \(O(n \log d)\) is used.
K booking
The only modification in book is to compare \(k>=K-1\).
Full solution
class segment_node:
def __init__(self, arange, val):
self.range = arange
self.lazy = None
self.val = val
self.left = None
self.right = None
def query(root, arange):
if root is None:
return 0
normalize(root)
i,j = arange
si,sj = root.range
if i>j or j<si or i>sj:
return 0
if i <= si and sj <= j:
return root.val
return max(query(root.left,arange),query(root.right,arange))
def update(root, arange, val):
if root is None:
return
i,j = arange
si,sj = root.range
if i>j or j<si or i>sj:
return
normalize(root)
if i<=si and sj<=j:
root.lazy = val
normalize(root)
return
update(root.left, arange, val)
update(root.right, arange, val)
root.val = max(root.left.val, root.light.val)
def normalize(root):
if root is None:
return
if root.lazy is not None:
root.val = root.val + root.lazy
si,sj = root.range
if si < sj:
if root.left is None or root.right is None:
mid = (si + sj) // 2
root.left = segment_node((si, mid), root.val)
root.right = segment_node((mid+1,sj), root.val)
elif root.lazy is not None:
root.left.lazy = root.left.lazy + root.lazy
root.right.lazy = root.right.lazy + root.lazy
root.lazy = None
class MyCalendarTwo:
def __init__(self):
self.root = segment_node((0,1000000000),0)
def book(self, start, end):
arange = (start,end)
k = query(self.root, arange)
if k >= 2:
return False
update(self.root, arange)
return True