题目来源:https://leetcode.cn/problems/my-calendar-i/
大致题意:
设置一个 MyCalendar 类,包含以下功能:
- MyCalendar() 初始化日历对象。
- boolean book(int start, int end) 如果可以将日程安排成功添加到日历中而不会导致重复预订,返回 true 。否则,返回 false 并且不要将该日程安排添加到日历中。
思路
使用线段树存下日程,每添加一次日程,将日程对应的区间在线段树中做标记。每次添加前,先检查当前日程区间内是否有被添加过的时间段,若有则不添加该日程。
因为线段树使用节点数目约是节点范围的 4 倍,而本体节点范围为 [0, 109],范围过大,不适合直接申请空间。所以采用了动态开点的方法,每放入一个日程,动态创建与日程区间相关的线段树节点。并使用懒标记标注当前节点所覆盖区间是否都已经安排过日程。
具体添加日程的操作如下:
- 先判断日程对应区间内是否有被添加过,查询时可以通过懒标记和查询区间内是否已经创建过节点判断:若有懒标记或者查询区间内有节点被创建,则该区间一定有被添加过的时间段
- 若整个区间没有被插入过,则动态开点插入该日程
代码:
public class MyCalendar_SegmentTree {
Set<Integer> tree; // 表示节点范围内有被预定的区间
Set<Integer> lazy; // 表示节点范围内都被预定
int BORDER; // 节点最大范围
public MyCalendar_SegmentTree() {
tree = new HashSet<>();
lazy = new HashSet<>();
BORDER = (int) 1e9;
}
public boolean book(int start, int end) {
// 首先查询当前日程区间内是否有被插入过
if (query(start, end - 1, 0, BORDER, 1)) {
return false;
}
// 插入当前日程
update(start, end - 1, 0, BORDER, 1);
return true;
}
/**
* 查询 [start, end] 区间是否有线段树节点,若有则表示该区间内有被预定的部分
*
* @param start
* @param end
* @param l 当前线段树节点的左边界
* @param r 当前线段树节点的右边界
* @param idx 当前线段树节点编号
* @return
*/
public boolean query(int start, int end, int l, int r, int idx) {
// 当前线段树节点不包含查询范围
if (r < start || end < l) {
return false;
}
// 若节点范围都被预定过,直接返回 true
if (lazy.contains(idx)) {
return true;
}
// 当前线段树节点的范围都在查询范围内
if (start <= l && r <= end) {
// 那么只要线段树节点存在,就表示该线段树节点范围被预定过
return tree.contains(idx);
}
int mid = (l + r) >> 1;
// 查询范围只在线段树左节点中
if (end <= mid) {
return query(start, end, l, mid, idx * 2);
} else if (mid < start) { // 查询范围只在线段树右节点中
return query(start, end, mid + 1, r, idx * 2 + 1);
} else { // 查询范围横跨线段树两个子节点
return query(start, end, l, mid, idx * 2) || query(start, end, mid + 1, r, idx * 2 + 1);
}
}
/**
* 预定区间 [start, end]
*
* @param start
* @param end
* @param l 当前线段树节点左边界
* @param r 当前线段树节点右边界
* @param idx 当前线段树节点编号
*/
public void update(int start, int end, int l, int r, int idx) {
if (r < start || end < l) {
return;
}
// 当前线段树节点的范围都在更新范围内
if (start <= l && r <= end) {
tree.add(idx);
lazy.add(idx);
} else {
int mid = (l + r) >> 1;
if (mid >= end) { // 更新范围只在线段树左节点
update(start, end, l, mid, idx * 2);
} else if (mid < start) { // 更新范围只在线段树右节点
update(start, end, mid + 1, r, idx * 2 + 1);
} else { // 更新范围横跨线段树左右节点
update(start, end, l, mid, idx * 2);
update(start, end, mid + 1, r, idx * 2 + 1);
}
// 当前线段树节点范围中有区间被预定
tree.add(idx);
}
}
public static void main(String[] args) {
MyCalendar_SegmentTree tree = new MyCalendar_SegmentTree();
System.out.println(tree.book(10, 20));
System.out.println(tree.book(15, 25));
System.out.println(tree.book(20, 30));
}
}