1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
class ExamRoom {
private Map<Integer, int[]> endMap;
private Map<Integer, int[]> startMap;
private TreeSet<int[]> intervals;
private int N;

public ExamRoom(int n) {
endMap = new HashMap<>();
startMap = new HashMap<>();
intervals = new TreeSet<>((a, b) -> {
int distanceA = distance(a);
int distanceB = distance(b);
if (distanceA != distanceB) {
return distanceA - distanceB;
}
return b[0] - a[0];
});
N = n;
intervals.add(new int[] { -1, N });
}

public int seat() {
Map<Integer, int[]> sM = this.startMap;
Map<Integer, int[]> eM = this.endMap;
TreeSet<int[]> inter = this.intervals;
int[] longestInterval = intervals.last();
int ans = -1;
if (longestInterval[0] == -1) {
ans = 0;
} else if (longestInterval[1] == N) {
ans = N - 1;
} else {
ans = (longestInterval[1] + longestInterval[0]) / 2;
}
removeInterval(longestInterval);
addInterval(new int[] { longestInterval[0], ans });
addInterval(new int[] { ans, longestInterval[1] });
return ans;
}

public void leave(int p) {
int[] leftInterval = endMap.get(p);
int[] rightInterval = startMap.get(p);
removeInterval(leftInterval);
removeInterval(rightInterval);
addInterval(new int[] { leftInterval[0], rightInterval[1] });
}

private void addInterval(int[] interval) {
startMap.put(interval[0], interval);
endMap.put(interval[1], interval);
intervals.add(interval);
}

private void removeInterval(int[] interval) {
startMap.remove(interval[0]);
endMap.remove(interval[1]);
intervals.remove(interval);
}

private int distance(int[] interval) {
if (interval[0] == -1) {
return interval[1];
}
if (interval[1] == N) {
return N - 1 - interval[0];
}
return (interval[1] - interval[0]) / 2;
}

private void print() {
System.out.println(startMap);
System.out.println(endMap);
System.out.println(intervals);
}
}

这道题很有意思, 属于那种我们边想边有问题, 然后怎么解决? 解决完后又有问题, 直到最后没问题了, 写的方法也就出来了. 开始我们需要知道哪个interval最长, 但是有个问题, 那就是最长的不一定满足题意.比如一个interval长度为3, 但是靠前. 一个interval长度为4靠后, 那么在这两个interval的中间安放一个人, 距离interval左端的距离都是2, 但是3更靠前, 因此需要选3. 这样的话, 本质就是谁距离左端最近. 我们于是要计算距离左端的距离而非interval的长度.

其次当我们插入一个人后, 之前的interval被一分为二. 我们需要把这个原来的较长的interval移除然后添加两个新的子intervals. 这没问题, 我们是知道原interval的以及要插入的点.

最后是我们如果移除一个人, 那么会将两个interval合并. 这要怎么做? 我们可以记录每个interval的start和end, 分别在map中. 也就是这两个map如果给它们一个start或者end, 它们就能告诉我们对应的interval是谁. 每个interval的start和别人肯定都不同, end也是一样的.

至此我们的逻辑就差不多了. 一个是需要能够告诉我们在available的intervals中候选的位置距离人最远, 如果有多个, 那就比较哪个interval靠前. 很自然的我们就想到使用TreeSet. 为什么不用PQ? 因为我们需要有时候移除非顶端的element. 然后用两个map来存所有intervals的start和end.

我们需要注意的是一开始我们要把人放到第0个位置, 下一个人放到最后一个位置. 这两个算是edge case. 我们如何解决这个问题呢? 就是按照我们的逻辑我们从set中找出一个最长的interval(这里的长指的是距离这个interval最左端的最远), 然后取中点, 之后把旧的interval移走, 新的两个interval加进来. 一个聪明的做法就是加入一个{-1, N}的pair. 然后如果出来的interval左端是-1, 那我们直接放到0的位置, 此时不能取该interval的中点, 最左端是距离其他人最远的地方. 如果interval出来右端是N, 那一定要放在N - 1处. 然后后面就移除旧interval添加新的两个interval就行了.

你可能会说, 有可能添加start和hend挨着的interval. 确实会出现这种情况, 但是由于我们是TreeSet, 这样的intervals肯定都在最后面.由于题目说seat的时候肯定有位置, 那我们就不会取到这些length是0的intervals了.

中间遇到了一个bug, 后来发现35行需要先remove再add, 如果先add, 再remove, 那会把add好的东西给移除走. add中会覆盖longestInterval两端在不同map中对应的interval, 然后我们再remove, 那之前add的东西就被remove走了. 这需要注意.

时间复杂度: seat是O(logn) leave是O(1)
空间复杂度: O(n) 因为可能每个点即是start又是end. 在map中和set中存满了.