在问 · 算法题解

729. 我的日程安排表 I

中等设计直接遍历力扣原题 ↗

题目描述

设计日程类 MyCalendar。每个日程用半开区间 [startTime, endTime) 表示,即包含所有满足 startTime <= x < endTime 的时刻 x。

MyCalendar():创建一个空日历。

book(startTime, endTime):尝试加入一个日程。如果它和已加入的某个日程有共同时刻,就是冲突,此时不加入并返回 false;否则加入日历并返回 true。

因为是半开区间,一个日程的结束时刻等于另一个的开始时刻时不算冲突。

示例 1
输入:操作 = ["MyCalendar","book","book","book"], 参数 = [[],[10,20],[15,25],[20,30]]
输出:[null,true,false,true]

解释:[15,25) 与 [10,20) 重叠被拒;[20,30) 只在端点 20 处相接,可以加入。

数据范围

  • 0 <= start < end <= 10^9
  • 每组测试中 book 最多被调用 1000 次。

解题思路:直接遍历

把成功的预订存在列表里。新区间 [start, end) 与已有区间 [s, e) 相交,当且仅当 s < end 且 start < e。遍历所有已有预订,只要碰到一个相交就拒绝;全部不相交才加入。

为什么正确

列表中的区间两两不相交。

复杂度分析

第 n 次调用 O(n),总计 O(n²);空间 O(n)。用有序结构二分可以做到单次 O(log n)。

易错点

区间是半开的,判断相交必须用严格小于:[10,20) 和 [20,30) 只是相接。只检查新区间的端点是否落在旧区间内是不够的,还会漏掉新区间完全包住旧区间的情况。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class MyCalendar:
    def __init__(self):
        self.booked = []

    def book(self, startTime: int, endTime: int) -> bool:
        for s, e in self.booked:
            if s < endTime and startTime < e:
                return False
        self.booked.append((startTime, endTime))
        return True

载入编辑器,自己跑一遍 →

其他解法

直接遍历代码最短,调用次数少时足够;有序集合 + 二分查找只检查两个邻居,调用次数多时明显更快。

解法 2:有序集合 + 二分查找 时间 O(n log n),空间 O(n)

已预订的区间互不重叠,按起点排序后终点也有序。新区间 [s, e) 只可能和两个邻居冲突:起点不小于 s 的第一个区间,需要它的起点 ≥ e;它前面的一个区间,需要它的终点 ≤ s。二分找到位置后检查这两个邻居,都不冲突就插入。

C++ 和 Java 用平衡树(map、TreeMap),单次是 O(log n)。Python、Go、JS 用有序数组,查找 O(log n),插入要搬移元素,最坏 O(n),但常数很小。

from bisect import bisect_left


class MyCalendar:
    def __init__(self):
        self.starts = []
        self.ends = []

    def book(self, startTime: int, endTime: int) -> bool:
        k = bisect_left(self.starts, startTime)
        if k < len(self.starts) and self.starts[k] < endTime:
            return False
        if k > 0 and self.ends[k - 1] > startTime:
            return False
        self.starts.insert(k, startTime)
        self.ends.insert(k, endTime)
        return True

设计 专题的其他题目

查看 设计 全部题目 →