区间交叉


1. 区间列表的交集

给定两个由一些 闭区间 组成的列表,每个区间列表都是成对不相交的,并且已经排序。

返回这两个区间列表的交集。

(形式上,闭区间 [a, b](其中 a <= b)表示实数 x 的集合,而 a <= x <= b。两个闭区间的交集是一组实数,要么为空集,要么为闭区间。例如,[1, 3] 和 [2, 4] 的交集为 [2, 3]。)

示例:

输入:A = [[0,2],[5,10],[13,23],[24,25]], B = [[1,5],[8,12],[15,24],[25,26]]
输出:[[1,2],[5,5],[8,10],[15,23],[24,24],[25,25]]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/interval-list-intersections
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路:前提,两个数组是排好序的数组。用双指针循环遍历,如果两个时间段的最大开始时间和最小结束时间,如果最大开始时间小于最小结束时间,则说明两个时间段有交集。取这个最大开始时间和最小结束时间作为时间段存起来。然后把结束时间段早的数组向后移一位。

class Solution {     public int[][] intervalIntersection(int[][] firstList, int[][] secondList) {         List interRange = new ArrayList<>();
        int i = 0, j = 0;
        while(i < firstList.length && j < secondList.length) {             int start = Math.max(firstList[i][0], secondList[j][0]);             int end = Math.min(firstList[i][1], secondList[j][1]);
            if(start <= end) {                 int[] range = new int[]{start, end};                 interRange.add(range);             }                          if(firstList[i][1] < secondList[j][1]) {                 i++;             } else {                 j++;             }         }
        int[][] result = new int[interRange.size()][];         for(int k = 0; k < interRange.size(); k++) {             result[k] = interRange.get(k);         }         return result;     } }  

2. 安排会议日程

你是一名行政助理,手里有两位客户的空闲时间表:slots1 和 slots2,以及会议的预计持续时间 duration,请你为他们安排合适的会议时间。

「会议时间」是两位客户都有空参加,并且持续时间能够满足预计时间 duration 的 最早的时间间隔。

如果没有满足要求的会议时间,就请返回一个 空数组。

「空闲时间」的格式是 [start, end],由开始时间 start 和结束时间 end 组成,表示从 start 开始,到 end 结束。 

题目保证数据有效:同一个人的空闲时间不会出现交叠的情况,也就是说,对于同一个人的两个空闲时间 [start1, end1] 和 [start2, end2],要么 start1 > end2,要么 start2 > end1。

示例 1:

输入:slots1 = [[10,50],[60,120],[140,210]], slots2 = [[0,15],[60,70]], duration = 8
输出:[60,68]
示例 2:

输入:slots1 = [[10,50],[60,120],[140,210]], slots2 = [[0,15],[60,70]], duration = 12
输出:[]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/meeting-scheduler
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

 public List minAvailableDuration(int[][] slots1, int[][] slots2, int duration) {         Arrays.sort(slots1, (a1, a2) -> a1[0] - a2[0]);         Arrays.sort(slots2, (b1, b2) -> b1[0] - b2[0]);
        List result = new ArrayList<>();
        int len1 = slots1.length;         int len2 = slots2.length;         int i = 0, j = 0;         while(i < len1 && j < len2) {             int start = Math.max(slots1[i][0], slots2[j][0]);             int end = Math.min(slots1[i][1], slots2[j][1]);             if(end - start >= duration) {                 result.add(start);                 result.add(start + duration);                 break;             }              if(slots1[i][1] > slots2[j][1]) {                 j++;             } else {                 i++;             }         }
        return result; }   3. 我的日程安排

实现一个 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)

示例 1:

MyCalendar();
MyCalendar.book(10, 20); // returns true
MyCalendar.book(15, 25); // returns false
MyCalendar.book(20, 30); // returns true
解释:
第一个日程安排可以添加到日历中. 第二个日程安排不能添加到日历中,因为时间 15 已经被第一个日程安排预定了。
第三个日程安排可以添加到日历中,因为第一个日程安排并不包含时间 20 。
说明:

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/my-calendar-i
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路:把每个日程放入TreeMap, 开始时间作为key, 结束时间作为值。TreeNap具有天然的排序功能。每次放入日程时找到开始时间的上下日程进行比价。

  class MyCalendar {     TreeMap treeMap;
    public MyCalendar() {         treeMap = new TreeMap<>();     }
    public boolean book(int start, int end) {         Integer preStart = treeMap.floorKey(start);         Integer postStart = treeMap.ceilingKey(start);
        if((preStart == null || treeMap.get(preStart) <= start)                 && (postStart == null || end <= postStart )) {             treeMap.put(start, end);             return true;         }
        return false;     } }   4. 我的日程安排2

实现一个 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)将和第二个日程安排双重预订。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/my-calendar-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路:要检查是否是三重预定,先找出双重预定的时间点,再用日程安排去和双重预定时间比较。

方法1:

public class MyCalendarTwo {
private List calendarList;
private List overlapList;

public MyCalendarTwo() {
calendarList = new ArrayList<>();
overlapList = new ArrayList<>();
}

public boolean book(int start, int end) {
for(int[] cal : overlapList) {
if(start < cal[1] && end > cal[0]) {
return false;
}
}

for(int[] cal : calendarList) {
if(start < cal[1] && end > cal[0]) {
overlapList.add(new int[] {Math.max(start, cal[0]), Math.min(end, cal[1])});
}
}

int[] calendar = new int[2];
calendar[0] = start;
calendar[1] = end;
calendarList.add(calendar);

return true;
}
}

方法2:使用TreeMap存储日程的开始和结束时间的key,开始记为1,结束记为-1. TreeMap是排序的,所以日程都是按时间进行的。如果有3个在同时开的会议,则返回false,否则为true.
class MyCalendarTwo {
    TreeMap calendars;
    public MyCalendarTwo() {         calendars = new TreeMap<>();     }
    public boolean book(int start, int end) {         calendars.put(start, calendars.getOrDefault(start,0) + 1);         calendars.put(end, calendars.getOrDefault(end, 0) - 1);
        int active = 0;         for(int val : calendars.values()) {             active += val;             if(active >= 3) {       //如果超过3个则把添加的日程删掉。                 calendars.put(start, calendars.get(start) - 1);                 calendars.put(end, calendars.get(end) + 1);                 if(calendars.get(start) == 0) {                     calendars.remove(start);                 }                 return false;             }         }
        return true;     } }   5. 我的日程安排3

实现一个 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次预订。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/my-calendar-iii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路: 使用TreeMap,记录下同时段开会的最大值。

class MyCalendarThree {     TreeMap calendars;
    public MyCalendarThree() {         calendars = new TreeMap<>();     }          public int book(int start, int end) {         calendars.put(start, calendars.getOrDefault(start, 0) + 1);         calendars.put(end, calendars.getOrDefault(end, 0) -1);         int count = 0;         int result = 0;         for(int val : calendars.values()) {             count += val;             result = Math.max(count, result);         }         return result;     } }    

855. 考场就座

在考场里,一排有 N 个座位,分别编号为 0, 1, 2, ..., N-1 。

当学生进入考场后,他必须坐在能够使他与离他最近的人之间的距离达到最大化的座位上。如果有多个这样的座位,他会坐在编号最小的座位上。(另外,如果考场里没有人,那么学生就坐在 0 号座位上。)

返回 ExamRoom(int N) 类,它有两个公开的函数:其中,函数 ExamRoom.seat() 会返回一个 int (整型数据),代表学生坐的位置;函数 ExamRoom.leave(int p) 代表坐在座位 p 上的学生现在离开了考场。每次调用 ExamRoom.leave(p) 时都保证有学生坐在座位 p 上。

示例:

输入:["ExamRoom","seat","seat","seat","seat","leave","seat"], [[10],[],[],[],[],[4],[]]
输出:[null,0,9,4,2,null,5]
解释:
ExamRoom(10) -> null
seat() -> 0,没有人在考场里,那么学生坐在 0 号座位上。
seat() -> 9,学生最后坐在 9 号座位上。
seat() -> 4,学生最后坐在 4 号座位上。
seat() -> 2,学生最后坐在 2 号座位上。
leave(4) -> null
seat() -> 5,学生最后坐在 5 号座位上。


class ExamRoom {     int N;     TreeSet seatSet;
    public ExamRoom(int N) {        this.N = N;        seatSet = new TreeSet();     }          public int seat() {                if(seatSet.size() == 0) {            seatSet.add(0);            return 0;        }             int preSeat = -1;        int distance = seatSet.first();        int pos = 0;
       for(int seat : seatSet) {            if(preSeat >= 0) {                 int dist = (seat - preSeat) / 2;
                if(dist > distance) {                     distance = dist;                     pos = preSeat + distance;                 }             }            preSeat = seat;        }

       if(N-1 - seatSet.last() > distance) {            pos = N - 1;        }
       seatSet.add(pos);        return pos;     }          public void leave(int p) {         seatSet.remove(p);     } }