【算法提升班】《個人日程安排表》系列

《個人日程安排表》截止目前(2020-02-03)在 LeetCode 上一共有三道題,其中兩個中等難度,一個困難難度,分別是:node

另外 LeetCode 上有一個相似的系列《會議室》,截止目前(2020-02-03)有兩道題目。其中一個簡單一箇中等,分別是:python

今天咱們就來攻克它們。segmentfault

729. 個人日程安排表 I

題目地址

https://leetcode-cn.com/probl...數組

題目描述

實現一個 MyCalendar 類來存放你的日程安排。若是要添加的時間內沒有其餘安排,則能夠存儲這個新的日程安排。數據結構

MyCalendar 有一個 book(int start, int end)方法。它意味着在 start 到 end 時間內增長一個日程安排,注意,這裏的時間是半開區間,即 [start, end), 實數  x 的範圍爲,  start <= x < end。app

當兩個日程安排有一些時間上的交叉時(例如兩個日程安排都在同一時間內),就會產生重複預訂。函數

每次調用 MyCalendar.book 方法時,若是能夠將日程安排成功添加到日曆中而不會致使重複預訂,返回 true。不然,返回 false  而且不要將該日程安排添加到日曆中。測試

請按照如下步驟調用 MyCalendar 類: MyCalendar cal = new MyCalendar(); MyCalendar.book(start, end)優化

示例 1:spa

MyCalendar();
MyCalendar.book(10, 20); // returns true
MyCalendar.book(15, 25); // returns false
MyCalendar.book(20, 30); // returns true
解釋:
第一個日程安排能夠添加到日曆中. 第二個日程安排不能添加到日曆中,由於時間 15 已經被第一個日程安排預約了。
第三個日程安排能夠添加到日曆中,由於第一個日程安排並不包含時間 20 。
說明:

每一個測試用例,調用  MyCalendar.book  函數最多不超過  100 次。
調用函數  MyCalendar.book(start, end)時, start 和  end 的取值範圍爲  [0, 10^9]。

暴力法

思路

首先咱們考慮暴力法。每插入一個元素咱們都判斷其是否和已有的全部課程重疊。

咱們定一個函數intersected(calendar, calendars),其中 calendar 是即將要插入的課程,calendars 是已經插入的課程。 只要 calendar 和 calendars 中的任何一個課程有交叉,咱們就返回 True,不然返回 False。

對於兩個 calendar,咱們的判斷邏輯都是同樣的。假設連個 calendar 分別是[s1, e1][s2, e2]。那麼若是s1 >= e2 or s2 <= e1, 則兩個課程沒有交叉,能夠預約,不然不能夠。如圖,1,2,3 能夠預約,剩下的不能夠。

image.png

代碼是這樣的:

def intersected(calendar, calendars):
        for [start, end] in calendars:
            if calendar[0] >= end or calendar[1] <= start:
                continue
            else:
                return True

        return False

複雜度分析:

  • 時間複雜度:$O(N^2)$。N 指的是平常安排的數量,對於每一個新的平常安排,咱們檢查新的平常安排是否發生衝突來決定是否能夠預訂新的平常安排。
  • 空間複雜度: $O(N)$。

這個代碼寫出來以後總體代碼就呼之欲出了,所有代碼見下方代碼部分。

代碼

代碼支持 Python3:

Python3 Code:

#
# @lc app=leetcode.cn id=729 lang=python3
#
# [729] 個人日程安排表 I
#

# @lc code=start


class MyCalendar:

    def __init__(self):
        self.calendars = []

    def book(self, start: int, end: int) -> bool:
        def intersected(calendar, calendars):
            for [start, end] in calendars:
                if calendar[0] >= end or calendar[1] <= start:
                    continue
                else:
                    return True

            return False
        if intersected([start, end], self.calendars):
            return False
        self.calendars.append([start, end])
        return True

        # Your MyCalendar object will be instantiated and called as such:
        # obj = MyCalendar()
        # param_1 = obj.book(start,end)
        # @lc code=end

實際上咱們還能夠換個角度,上面的思路判斷交叉部分咱們考慮的是「如何不交叉」,剩下的就是交叉。咱們也能夠直接考慮交叉。仍是上面的例子,若是兩個課程交叉,那麼必定知足s1 < e2 and e1 > s2。基於此,咱們寫出下面的代碼。

代碼支持 Python3:

Python3 Code:

#
# @lc app=leetcode.cn id=729 lang=python3
#
# [729] 個人日程安排表 I
#

# @lc code=start


class MyCalendar:

    def __init__(self):
        self.calendars = []

    def book(self, start: int, end: int) -> bool:
        for s, e in self.calendars:
            if start < e and end > s:
                return False
        self.calendars.append([start, end])
        return True

        # Your MyCalendar object will be instantiated and called as such:
        # obj = MyCalendar()
        # param_1 = obj.book(start,end)
        # @lc code=end

二叉查找樹法

思路

和上面思路相似,只不過咱們每次都對 calendars 進行排序,那麼咱們能夠經過二分查找日程安排的狀況來檢查新平常安排是否能夠預訂。若是每次插入以前都進行一次排序,那麼時間複雜度會很高。如圖,咱們的[s1,e1], [s2,e2], [s3,e3] 是按照時間順序排好的日程安排。咱們如今要插入[s,e],咱們使用二分查找,找到要插入的位置,而後和插入位置的課程進行一次比對便可,這部分的時間複雜度是 O(logN)&dollar;。

image.png

咱們考慮使用平衡二叉樹來維護這種動態的變化,在最差的狀況時間複雜度會退化到上述的$O(N^2)$,平均狀況是$O(NlogN)$,其中 N 是已預訂的平常安排數。

image.png

代碼

代碼支持 Python3:

Python3 Code:

class Node:
    def __init__(self, start, end):
        self.start = start
        self.end = end
        self.left = self.right = None

    def insert(self, node):
        if node.start >= self.end:
            if not self.right:
                self.right = node
                return True
            return self.right.insert(node)
        elif node.end <= self.start:
            if not self.left:
                self.left = node
                return True
            return self.left.insert(node)
        else:
            return False

class MyCalendar(object):
    def __init__(self):
        self.root = None

    def book(self, start, end):
        if self.root is None:
            self.root = Node(start, end)
            return True
        return self.root.insert(Node(start, end))

731. 個人日程安排表 II

題目地址

https://leetcode-cn.com/probl...

題目描述

實現一個 MyCalendar 類來存放你的日程安排。若是要添加的時間內不會致使三重預訂時,則能夠存儲這個新的日程安排。

MyCalendar 有一個 book(int start, int end)方法。它意味着在 start 到 end 時間內增長一個日程安排,注意,這裏的時間是半開區間,即 [start, end), 實數  x 的範圍爲,  start <= x < end。

當三個日程安排有一些時間上的交叉時(例如三個日程安排都在同一時間內),就會產生三重預訂。

每次調用 MyCalendar.book 方法時,若是能夠將日程安排成功添加到日曆中而不會致使三重預訂,返回 true。不然,返回 false 而且不要將該日程安排添加到日曆中。

請按照如下步驟調用 MyCalendar 類: MyCalendar cal = new MyCalendar(); MyCalendar.book(start, end)

示例:

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
解釋:
前兩個日程安排能夠添加至日曆中。 第三個日程安排會致使雙重預訂,但能夠添加至日曆中。
第四個日程安排活動(5,15)不能添加至日曆中,由於它會致使三重預訂。
第五個日程安排(5,10)能夠添加至日曆中,由於它未使用已經雙重預訂的時間 10。
第六個日程安排(25,55)能夠添加至日曆中,由於時間 [25,40] 將和第三個日程安排雙重預訂;
時間 [40,50] 將單獨預訂,時間 [50,55)將和第二個日程安排雙重預訂。

提示:

每一個測試用例,調用  MyCalendar.book  函數最多不超過  1000 次。
調用函數  MyCalendar.book(start, end)時, start 和  end 的取值範圍爲  [0, 10^9]。

暴力法

思路

暴力法和上述思路相似。可是咱們多維護一個數組 intersectedCalendars 用來存儲二次預約的日程安排。若是課程第一次衝突,咱們將其加入 intersectedCalendars,若是和 intersectedCalendars 也衝突了,說明出現了三次預約,咱們直接返回 False。

代碼

代碼支持 Python3:

Python3 Code:

class MyCalendarTwo:

    def __init__(self):
        self.calendars = []
        self.intersectedCalendars = []

    def book(self, start: int, end: int) -> bool:
        for [s, e] in self.intersectedCalendars:
            if start < e and end > s:
                return False
        for [s, e] in self.calendars:
            if start < e and end > s:
                self.intersectedCalendars.append([max(start, s), min(end, e)])
        self.calendars.append([start, end])
        return True

二叉查找樹法

和上面的題目相似,咱們仍然可使用平衡二叉樹來簡化查找邏輯。具體能夠參考這個 discussion

每次插入以前咱們都須要進行一次判斷,判斷是否能夠插入。若是不能夠插入,直接返回 False,不然咱們進行一次插入。 插入的時候,若是和已有的相交了,咱們判斷是否以前已經相交了一次,若是是返回 False,不然返回 True。關於如何判斷是否和已有的相交,咱們能夠在 node 節點增長一個字段的方式來標記,在這裏咱們使用 single_overlap,True 表示產生了二次預約,False 則表示沒有產生過兩次及以上的預約。

代碼

代碼支持 Python3:

Python3 Code:

class Node:
    def __init__(self, start, end):
        self.start = start
        self.end = end
        self.left = None
        self.right = None
        self.single_overlap = False

class MyCalendarTwo:

    def __init__(self):
        self.root = None

    def book(self, start, end):
        if not self.canInsert(start, end, self.root):
            return False

        self.root = self.insert(start, end, self.root)
        return True


    def canInsert(self, start, end, root):
        if not root:
            return True

        if start >= end:
            return True

        if end <= root.start:
            return self.canInsert(start, end, root.left)

        elif start >= root.end:
            return self.canInsert(start, end, root.right)

        else:
            if root.single_overlap:
                return False
            elif start >= root.start and end <= root.end:
                return True
            else:
                return self.canInsert(start, root.start, root.left) and self.canInsert(root.end, end, root.right)



    def insert(self, start, end, root):
        if not root:
            root = Node(start, end)
            return root

        if start >= end:
            return root

        if start >= root.end:
            root.right = self.insert(start, end, root.right)

        elif end <= root.start:
            root.left = self.insert(start, end, root.left)

        else:
            root.single_overlap = True
            a = min(root.start, start)
            b = max(root.start, start)
            c = min(root.end, end)
            d = max(root.end, end)
            root.start, root.end = b, c
            root.left, root.right = self.insert(a, b, root.left), self.insert(c, d, root.right)

        return root

# Your MyCalendarTwo object will be instantiated and called as such:
# obj = MyCalendarTwo()
# param_1 = obj.book(start,end)

732. 個人日程安排表 III

題目地址

https://leetcode-cn.com/probl...

題目描述

實現一個 MyCalendar 類來存放你的日程安排,你能夠一直添加新的日程安排。

MyCalendar 有一個 book(int start, int end)方法。它意味着在 start 到 end 時間內增長一個日程安排,注意,這裏的時間是半開區間,即 [start, end), 實數  x 的範圍爲,  start <= x < end。

當 K 個日程安排有一些時間上的交叉時(例如 K 個日程安排都在同一時間內),就會產生 K 次預訂。

每次調用 MyCalendar.book 方法時,返回一個整數 K ,表示最大的 K 次預訂。

請按照如下步驟調用 MyCalendar 類: MyCalendar cal = new MyCalendar(); MyCalendar.book(start, end)

示例 1:

MyCalendarThree();
MyCalendarThree.book(10, 20); // returns 1
MyCalendarThree.book(50, 60); // returns 1
MyCalendarThree.book(10, 40); // returns 2
MyCalendarThree.book(5, 15); // returns 3
MyCalendarThree.book(5, 10); // returns 3
MyCalendarThree.book(25, 55); // returns 3
解釋:
前兩個日程安排能夠預訂而且不相交,因此最大的 K 次預訂是 1。
第三個日程安排[10,40]與第一個日程安排相交,最高的 K 次預訂爲 2。
其他的日程安排的最高 K 次預訂僅爲 3。
請注意,最後一第二天程安排可能會致使局部最高 K 次預訂爲 2,但答案仍然是 3,緣由是從開始到最後,時間[10,20],[10,40]和[5,15]仍然會致使 3 次預訂。
說明:

每一個測試用例,調用  MyCalendar.book  函數最多不超過  400 次。
調用函數  MyCalendar.book(start, end)時, start 和  end 的取值範圍爲  [0, 10^9]。

二叉查找樹法

思路

咱們仍然可使用上述的平衡二叉樹的作法。只不過咱們須要額外維護一個全局的最大值「k」,表示須要多少個預約。最終咱們返回 k。 同時每個 node 咱們都增長一個屬性 k,用來表示局部的最大值,對於每次插入,咱們將 node 的 k 和所有的 k 進行比較,取出最大值便可。

代碼

代碼支持 Python3:

Python3 Code:

class Node(object):
    def __init__(self, start, end, ktime=1):
        self.k = ktime
        self.s = start
        self.e = end
        self.right = None
        self.left = None

class MyCalendarThree(object):

    def __init__(self):
        self.root = None
        self.k = 0

    def book(self, start, end):
        self.root = self.insert(self.root, start, end, 1)
        return self.k
    def insert(self, root, start, end, k):
        if start >= end:
            return root
        if not root:
            self.k = max(self.k, k)
            return Node(start, end, k)
        else:
            if start >= root.e:
                root.right = self.insert(root.right, start, end, k)
                return root
            elif end <= root.s:
                root.left = self.insert(root.left, start, end, k)
                return root
            else:

                a = min(root.s, start)
                b = max(root.s, start)
                c = min(root.e, end)
                d = max(root.e, end)

                root.left = self.insert(root.left, a, b, a == root.s and root.k or k)
                root.right = self.insert(root.right, c,d, d == root.e and root.k or k)
                root.k += k
                root.s = b
                root.e = c
                self.k = max(root.k, self.k)
                return root

Count Map 法

思路

這個是我在看了 Discussion [[C++] Map Solution, beats 95%+](https://leetcode.com/problems... 以後寫的解法,解法很是巧妙。

咱們使用一個 count map 來存儲全部的預約,對於每次插入,咱們執行count[start] += 1count[end] -= 1。 count[t] 表示從 t 開始到下一個 t 咱們有幾個預約。所以咱們須要對 count 進行排序才行。 咱們維護一個最大值來 cnt 來表示須要的預約數。

好比預約[1,3]和[5,7],咱們產生一個預約便可:

image.png

再好比預約[1,5]和[3,7],咱們須要兩個預約:

image.png

咱們可使用紅黑樹來簡化時間複雜度,若是你使用的是 Java,能夠直接使用現成的數據結構 TreeMap。我這裏偷懶,每次都排序,時間複雜度會很高,可是能夠 AC。

讀到這裏,你可能會發現: 這個解法彷佛更具備通用型。對於第一題咱們能夠判斷 cnt 是否小於等於 1,對於第二題咱們能夠判斷 cnt 是否小於等於 2。

若是你不借助紅黑樹等數據結構直接使用 count-map 法,即每次都進行一次排序,第一題和第二題可能會直接超時。

代碼

代碼支持 Python3:

Python3 Code:

class MyCalendarThree:

    def __init__(self):
        self.count = dict()

    def book(self, start: int, end: int) -> int:
        self.count[start] = self.count.get(start, 0) + 1
        self.count[end] = self.count.get(end, 0) - 1
        cnt = 0
        cur = 0

        for k in sorted(self.count):
            cur += self.count[k]
            cnt = max(cnt, cur)
        return cnt

        # Your MyCalendarThree object will be instantiated and called as such:
        # obj = MyCalendarThree()
        # param_1 = obj.book(start,end)

相關題目

LeetCode 上有一個相似的系列《會議室》,截止目前(2020-02-03)有兩道題目。其中一個簡單一箇中等,解題思路很是相似,你們用這個解題思路嘗試一下,檢測一下本身是否已經掌握。兩道題分別是:

總結

咱們對 LeetCode 上的專題《個人日程安排》的三道題進行了彙總。對於區間判斷是否重疊,咱們能夠反向判斷,也能夠正向判斷。 暴力的方法是每次對全部的課程進行判斷是否重疊,這種解法能夠 AC。咱們也能夠進一步優化,使用二叉查找樹來簡化時間複雜度。最後咱們介紹了一種 Count-Map 方法來通用解決全部的問題,不只能夠完美解決這三道題,還能夠擴展到《會議室》系列的兩道題。

相關文章
相關標籤/搜索