题目
在考场里,一排有 N 个座位,分别编号为 0, 1, 2, …, N-1 。
当学生进入考场后,他必须坐在能够使他与离他最近的人之间的距离达到最大化的座位上。如果有多个这样的座位,他会坐在编号最小的座位上。(另外,如果考场里没有人,那么学生就坐在 0 号座位上。)
返回 ExamRoom(int N) 类,它有两个公开的函数:其中,函数 ExamRoom.seat() 会返回一个 int (整型数据),代表学生坐的位置;函数 ExamRoom.leave(int p) 代表坐在座位 p 上的学生现在离开了考场。每次调用 ExamRoom.leave§ 时都保证有学生坐在座位 p 上。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/exam-room
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
代码
class ExamRoom {
// 将端点 p 映射到以 p 为左端点的线段
private Map<Integer,int[]> startMap;
// 将端点 p 映射到以 p 为右端点的线段
private Map<Integer,int[]> endMap;
// 根据线段长度从小到大存放所有线段
private TreeSet<int[]> pq;
private int N;
public ExamRoom(int N) {
this.N=N;
startMap=new HashMap<>();
endMap=new HashMap<>();
pq =new TreeSet<>(new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
// 算出两个线段的长度
int distA = distance(o1);
int distB = distance(o2);
// 如果长度相同,就比较索引
if (distA==distB) return o2[0]-o1[0];
// 长度更长的更大,排后面
return distA-distB;
}
});
// 在有序集合中先放一个虚拟线段
addInterval(new int[]{-1,N});
}
public int seat() {
// 从有序集合拿出最长的线段
int[] longest =pq.last();
int x =longest[0];
int y= longest[1];
int seat;
if (x==-1){//没人
seat=0;
}else if (y==N){//只有前头有人
seat=N-1;
}else {//在中间坐
seat=(y-x)/2+x;
}
// 将最长的线段分成两段
int[] left=new int[]{x,seat};
int[] right=new int[]{seat,y};
removeInterval(longest);
addInterval(left);//去除原来的线段
addInterval(right);
return seat;
}
public void leave(int p) {
// 将 p 左右的线段找出来
int[] right= startMap.get(p);
int[] left =endMap.get(p);
// 合并两个线段成为一个线段
int[] merged=new int[]{left[0],right[1]};
removeInterval(left);
removeInterval(right);
addInterval(merged);
}
/* 去除一个线段 */
private void removeInterval(int[] intv){
pq.remove(intv);
startMap.remove(intv[0]);
endMap.remove(intv[1]);
}
/* 增加一个线段 */
private void addInterval(int[] intv){
pq.add(intv);
startMap.put(intv[0],intv);
endMap.put(intv[1],intv);
}
/* 计算一个线段的长度 */
private int distance(int[] intv){
int x=intv[0];
int y=intv[1];
if (x==-1) return y/2;
if (y==N) return (N-1-x)/2;
// 中点和端点之间的长度
return (y-x)/2;
}
}
要点
- 们使用的 TreeSet 就是一个有序集合,储存最长线段
- 再用两个哈希维护头结点和尾结点
- 用一个「虚拟线段」让算法正确启动
- 找最长的线段,从中间分隔成两段,中点就是 seat() 的返回值;找 p 的左右线段,合并成一个线段,这就是leave(p)的逻辑
- 要求多个选择时选择索引最小的那个座位,修改有序数据结构的排序方式,并且 distance 函数,不能简单地让它计算一个线段两个端点间的长度,而是让它计算该线段中点和端点之间的长度