-
如题:
-
思路分析:
-
我们每次挑选座位的时候,需要里最近的人距离最大,也就是说如果要坐在两个人之间,应该坐在中点的位置
-
因此我们可以将两个座位抽象成一个线段,第一个人是线段开始位置start,第二个人是线段结束位置end,线段长度是end-start
-
此时我们就应该坐在线段的中点,离最近的人的距离就是线段长度
length/2
,因此我们要找出里最近的人的距离最大的位置=找出最大的那条线段 -
此时就应该考虑能够动态找出最值的数据结构,一般是堆和平衡二叉树,但是堆只能操作堆顶元素,平衡二叉树可以操作根结点也可以操作孩子结点。由于我们不但需要添加座位,还需要随机离开某座位的时候删除该座位,因此只能操作最值的堆不可用,应该用平衡二叉树
-
java中操作平衡二叉树的数据结构有
TreeSet
和TreeMap
,这里使用TreeSet
-
由于我们存放的是线段,这里可以用一个专门的内部类来表示,属性就包含线段开始位置start线段结束位置end,和线段长度end-start
- 注意:两个座位形成的线段之间不能有其他座位
static class Site { private int start; private int end; private int length; public Site(int start, int end, int length) { this.start = start; this.end = end; this.length = length; } }
也可以使用数组表示,但是可读性没那么好
-
-
代码
class ExamRoom { // 因为需要用leave()删除座位,例如此时座位是[1,3,5],我们要删除座位3,那么[1,3]和[3,5]应该合成[1,5], // 此时需要根据座位号3找出[1,3]和[3,5],然后在平衡二叉树中删除它,左边的线段以3为end,右边的线段以3为start // 因此我们用两个hashMap分别保存以3为start的线段和以3为end的线段 private Map<Integer, Site> startMap; private Map<Integer, Site> endMap; //平衡二叉树 private TreeSet<Site> tree; //将构造器中的n作为属性,供其他方法使用 private int N; public ExamRoom(int n) { N = n; startMap = new HashMap<>(); endMap = new HashMap<>(); //我们每次需要找一个新座位的时候,就是找出一条长度最大的线段,然后坐在该线段中间length/2的位置,因此我们比较器直接比较length/2的大小 //注意:0和N两个最左和最右座位,因为最开始选的时候必须选0,此时还无法构成线段,因此考虑加入两个哨兵座位-1和N,他们构成初始线段[-1,N], //而我们实际座位应该在[0,N-1]之间 //注意:普通情况下我们比较器比较两个元素的length/2的大小,因为我们选了这条线段就必须坐中间length/2的位置,保证离两端最远,但是 // 如果有一个元素的开始或结束位置是哨兵位置,那么我们选该线段就不用坐中间,我们坐哨兵位置边上,因为哨兵不是座位,因此此时比较的是length-1(除开哨兵) tree = new TreeSet<>((a, b) -> { int lengthA = 0; int lengthB = 0; //如果a包含哨兵 if (a.start == -1 || a.end == N) lengthA = a.length - 1; else lengthA = a.length / 2; //如果b包含哨兵 if (b.start == -1 || b.end == N) lengthB = b.length - 1; else lengthB = b.length / 2; //如果长度一样就按起始位置start从小到大排序 if (lengthA == lengthB)return a.start - b.start; //否则按长度从大到小排序 return lengthB - lengthA; }); //初试线段,起始结束位置都是哨兵,线段长度是end-start Site start = new Site(-1, N, N + 1); //初始化tree和两个map tree.add(start); startMap.put(start.start, start); endMap.put(start.end, start); } public int seat() { //选座位的时候挑出最大线段,坐中间,此时产生会两个新的线段[start,mid],[mid,end],记录下来 Site site1 = null; Site site2 = null; //挑出最大线段 Site site = tree.pollFirst(); //上面提到过,如果线段包含哨兵,那么就不是坐中间,而是做哨兵边上 if (site.start == -1) {//start是哨兵 site1 = new Site(-1, 0, 1); site2 = new Site(0, site.end, site.end); } //end是哨兵 else if (site.end == N) { site1 = new Site(site.start, N - 1, (N - 1) - site.start); site2 = new Site(N - 1, N, 1); } //都不是哨兵,坐中间 else { site1 = new Site(site.start, site.start + site.length / 2, site.length / 2); site2 = new Site(site.start + site.length / 2, site.end, site.end - site.start - site.length / 2); } //将以该线段从两个hashMap中移出 startMap.remove(site.start); endMap.remove(site.end); //在平衡二叉树中加入新生成的两个线段 tree.add(site1); tree.add(site2); //在两个hashMap中加入新生成的两个线段 startMap.put(site1.start, site1); endMap.put(site1.end, site1); startMap.put(site2.start, site2); endMap.put(site2.end, site2); //返回新挑选的座位号,site1.end=site2.start,返回哪一个都一样 return site1.end; } //离开座位的时候应该将该座位左右两端的线段合并 public void leave(int p) { //先从两个hashMap中移出以该座位号为开始的线段和以该座位为结束的线段,然后得到这两个线段,在tree中也删除这两个线段 Site site0 = startMap.remove(p); Site site1 = endMap.remove(p); tree.remove(site0); tree.remove(site1); //将两个线段合并,生成新线段,然后加入tree和两个hashMap中 Site site = new Site(site1.start, site0.end, site0.end - site1.start); tree.add(site); startMap.put(site.start,site); endMap.put(site.end,site); } static class Site { private int start; private int end; private int length; public Site(int start, int end, int length) { this.start = start; this.end = end; this.length = length; } } }
-
注意事项:我们
TreeSet
中的比较器比较的是length/2
,不能比较length
。- 因为如果有线段
[0,4]
和[5,10]
,那么比较长度的话[5,10]
长度是5,[0,4]
长度是4,我们会选择[5,10]
,但是5是奇数,5/2=2,我们和最近的人距离是2。 - 而
[0,4]
的长度4是偶数,4/2=2,我们和最近的人的距离也是2。 - 根据题意,距离一样我们应该选靠前的座位,因此此时应该选
[0,4]
,选[0,5]
就错了
- 因为如果有线段
09-26
296
07-31
113