携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情
每日刷题 2022.08.04
- leetcode原题链接:leetcode.cn/problems/my…
- 难度:中等
- 方法:二分
题目
- 实现一个 MyCalendar 类来存放你的日程安排。如果要添加的日程安排不会造成 重复预订 ,则可以存储这个新的日程安排。
- 当两个日程安排有一些时间上的交叉时(例如两个日程安排都在同一时间内),就会产生 重复预订 。
- 日程可以用一对整数 start 和 end 表示,这里的时间是半开区间,即
[start, end), 实数 x 的范围为,start <= x < end。 - 实现 MyCalendar 类:
- MyCalendar() 初始化日历对象。
- boolean book(int start, int end) 如果可以将日程安排成功添加到日历中而不会导致重复预订,返回 true 。否则,返回 false 并且不要将该日程安排添加到日历中。
示例
输入:
["MyCalendar", "book", "book", "book"]
[[], [10, 20], [15, 25], [20, 30]]
输出:
[null, true, false, true]
解释:
MyCalendar myCalendar = new MyCalendar();
myCalendar.book(10, 20); // return True
myCalendar.book(15, 25); // return False ,这个日程安排不能添加到日历中,因为时间 15 已经被另一个日程安排预订了。
myCalendar.book(20, 30); // return True ,这个日程安排可以添加到日历中,因为第一个日程安排预订的每个时间都小于 20 ,且不包含时间 20 。
提示
0 <= start < end <= 10^9- 每个测试用例,调用
book方法的次数最多不超过1000次。
解题思路
- 直接模拟也是可以解题的,因为
book一共会被调用1000次,那么也就是最坏的情况,所有的日程都可以安排到日历中,那么每次book调用就要遍历1000个日程,一共也就是10 ^ 6(比10 ^ 8小),因此是可以暴力通过的。 - 但是在我们自己分析样例的时候可以发现,如果日历中的日程都是有序的,那么我们就可以快速的找到最接近当前要插入的日程,判断其前后两个日程是否会覆盖当前的日程。
- 会被覆盖的两种情况(如下图所示)
深入分析
- 使用需要插入的日程的开始日期
start,作为二分的筛选条件,找到第一个大于start的日程下标,记为nextIdx。由此可知第一个小于等于start的日程的下标为preIdx = nextIdx - 1。 - 此时还需要考虑两个边界问题:(
n表示整个日历的长度)nextIdx = n是数组最后一个元素的下一个位置,也就是整个日历中不存在大于start的日程。可以只考虑前一个preIdx和start日程是否存在重叠。preIdx = -1是数组最开始的元素的前一个位置,也就是整个日历中不存在小于等于start的日程,可以只考虑后一个nextIdx和start日程是否存在重叠。
- 最后一种情况:
start在日历中既有小于等于的日程 也有 大于的日程,那么就需要判断start日程是否和这两个日程存在重叠。
新学习的方法splice
splice(2, 0, 'alice')表示:在数组下标为2的位置,删除0个元素,添加字符串alice
var myFish = ["angel", "clown", "mandarin", "sturgeon"];
var removed = myFish.splice(2, 0, "drum");
// 运算后的 myFish: ["angel", "clown", "drum", "mandarin", "sturgeon"]
// 被删除的元素: [], 没有元素被删除
- 当然也可以将索引出的数值删除,返回删除后的元素
var myFish = ['angel', 'clown', 'drum', 'sturgeon'];
var removed = myFish.splice(2, 1, "trumpet");
// 运算后的 myFish: ["angel", "clown", "trumpet", "sturgeon"]
// 被删除的元素: ["drum"]
AC代码
var MyCalendar = function() {
this.arr = [];
};
/**
* @param {number} start
* @param {number} end
* @return {boolean}
*/
MyCalendar.prototype.book = function(start, end) {
const n = this.arr.length;
if(n === 0) {
this.arr.push([start, end]);
return true;
}
// start <= a < end
// 二分查找
// console.log(this.binary(start));
let nextIdx = this.binary(start), preIdx = nextIdx - 1;
// console.log('idx:::', nextIdx, preIdx, this.arr)
// 如果下一个节点不存在,就是超出了数组中的最后一个节点,只需要与前一个节点进行比对就可以
if(n === nextIdx) {
// 只需要与前一个节点比较
if (this.arr[preIdx][1] <= start) {
// 可以插入
this.arr.push([start, end]);
return true;
}
return false;
} else {
if(preIdx === -1) {
// 如果前一个节点不存在,一样的,比较后面的就可以
if(end <= this.arr[nextIdx][0]){
// this.arr.unshift([start, end]);
this.arr.splice(0, 0, [start, end])
return true;
}
return false;
} else {
// 两个节点都存在,进行比较
if(this.arr[preIdx][1] <= start && end <= this.arr[nextIdx][0]) {
this.arr.splice(nextIdx, 0, [start, end]);
return true;
}
return false;
}
}
};
MyCalendar.prototype.binary = function (cur) {
// 查找到第一个大于当前开始值的开始节点
const n = this.arr.length, a = this.arr;
let left = -1, right = n;
while(left + 1 != right) {
let mid = Math.floor((left + right) / 2);
if(a[mid][0] <= cur) {
left = mid;
}else {
right = mid;
}
}
return right;
}
/**
* Your MyCalendar object will be instantiated and called as such:
* var obj = new MyCalendar()
* var param_1 = obj.book(start,end)
*/