[二分查找区间] 729. 我的日程安排表 I

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情

每日刷题 2022.08.04

题目

  • 实现一个 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小),因此是可以暴力通过的。
  • 但是在我们自己分析样例的时候可以发现,如果日历中的日程都是有序的,那么我们就可以快速的找到最接近当前要插入的日程,判断其前后两个日程是否会覆盖当前的日程。
  • 会被覆盖的两种情况(如下图所示) 1659627531722(1).png 1659627580963(1).png

深入分析

  • 使用需要插入的日程的开始日期start,作为二分的筛选条件,找到第一个大于start的日程下标,记为nextIdx。由此可知第一个小于等于start的日程的下标为preIdx = nextIdx - 1
  • 此时还需要考虑两个边界问题:(n表示整个日历的长度)
    • nextIdx = n是数组最后一个元素的下一个位置,也就是整个日历中不存在大于start的日程。可以只考虑前一个preIdxstart日程是否存在重叠。
    • preIdx = -1是数组最开始的元素的前一个位置,也就是整个日历中不存在小于等于start的日程,可以只考虑后一个nextIdxstart日程是否存在重叠。
  • 最后一种情况: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)
 */
复制代码

猜你喜欢

转载自juejin.im/post/7128046223503654943