当前位置: 首页 > news >正文

5.10如何调度考生的座位

5.10 如何调度考生的座位

5.10.1 问题描述

在考场里,一排有 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/problems/exam-room
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

public class ExamRoom {
    //构造函数,传入座位的总数N
    public ExamRoom(int n) {

    }
    //来了一名考生,返回你给它分配的作为
    public int seat() {
        
    }
    //坐在p位置的考生离开了
    public void leave(int p) {

    }
}
  • 假设有一个考场,考场一排一共N个座位,索引分别是[0...N-1],考生会陆续进入考场考生,并且可能在任何时候离开考场,安排座位时满足:每当一个学生进入时,你需要最大化地和最近其他人的距离,如果有多个这样的座位,安排到他的索引最小的那个座位

样例分析:

比如考场有5个座位,索引分别是[0,1,2,3,4]

第一名考生进入时,调用seat(),坐在任何都行,但是要给他安排索引最小的位置,也就是返回数字0

第二名考生进入时,再调用seat(),要和旁边的人距离最远,和0最远的是4

第三名考试进入时,再调用seat(),要和旁边的人距离最远,取得2

如果再进一名学生,可以坐在座位1或者3,取较小的索引1

如果将每两个相邻的考生看作是线段的两个端点,新安排的考生就是找最长的线段,然后让考生在中间在中间把这个线段给二分,中间就是给他分配的座位,leave§其实就是移出端点p,使得相邻两个线段合并为1个

思维点:

[1] 为啥是最长的线段?因为题目要求了距离要和其他考生最远,那么当且仅当在最长的线段上取点的时候才有可能取到最长的点

[2] 为啥是中间? 同样是因为要求与其他考生最远,如果偏左,那么就和左边那个同学近,偏右同理,因此二分取中间点是符合条件的最优解

5.10.2 思路分析

  • 首先需要把坐在教室里的学生抽象成线段,可以简单让一个大小为2的数组存储线段的两个端点索引,把它看作为一条线段,另外需要我们找到最长的线段,还需要去除线段,增加线段

**但凡在动态过程中取最值的要求,肯定要使用有序的数据结构,常用的数据结构就是二叉堆和平衡二叉搜索树。**二叉堆实现的优先级队列取最值的时间复杂度是O(logN),但是只能删除最大值。平衡二叉树也可以取最值,也可以修改、删除任意一个值,而且时间复杂度都是O(logN)

综上所述,二叉堆不能够满足leave操作,应该要使用平衡二叉树,所以这里会用到java的一种数据结构TreeSet,这是一种有序的数据结构,底层由红黑数(一种平衡二叉树)维护其有序性

哈希集合/映射底层是由哈希函数和数组实现的,特性是遍历无固定的顺序,但是操作效率高,时间复杂度为O(1)

集合/映射还可以依赖其他底层的数据结构,常见的就是红黑树,特性是自动维护其中的元素,操作效率是O(logN)这种一般称为"有序集合/映射"

本题中将使用的TreeSet就是一个有序集合,目的就是为了保持线段长度的有序性,快速查找最大线段,快速删除和插入

5.10.3 简化问题

先暂时抛开最小索引的要求,先实现最关键的需求。与之前的那道戳气球类似,我们这里要使用到一个技巧: 虚拟线段,因为题目的base-case是:当没有线段端点存在的时候,第一个线段端点自动放到0,第二个线段端点自动放到n-1的位置上,这种base-case并不是一劳永逸的,因为考生随时可能离开座位,然后回归到base-case,我们希望能够找到一种处理方法,使得base-case也能归到基础问题的处理中

    //将端点p映射到以p为左端点的线段
    private Map<Integer, int[]> startMap;
    //将端点p映射到以p为右端点的线段
    private Map<Integer, int[]> endMap;
    //根据线段大小长度从小到大存放所有的线段
    private TreeSet<int[]> pq;
    private int n;

    //构造函数,传入座位的总数N
    public ExamRoom(int n) {
        this.n = n;
        startMap = new HashMap<>();
        endMap = new HashMap<>();
        pq = new TreeSet<>((a,b)->{
           //算出两个线段的长度
           int distA =  distance(a);
           int distB =  distance(b);
           //长度更长的更大,排后面
            if(distA == distB){
                return b[0] - a[0];
            }
            return distA-distB;
        });
        //在有序集合中存放一个虚拟线段
        this.addInterval(new int[]{-1,this.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 == this.n){
            //情况二:最右边没人,则分配右边的座位
            seat  = this.n-1;
        }else{
            //不是边界的话就坐到中间去
            seat = x + (y-x)/2;
        }
        //新建了端点后,要维护一条新的线段
        int[] left = new int[]{x,seat};
        int[] right = new int[]{seat,y};
        removeInterval(longest);
        addInterval(left);
        addInterval(right);
        return seat;
    }
    //坐在p位置的考生离开了
    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;
        }
        if(y == this.n){
            return this.n-(x+1);
        }
        //中点和端点之间的长度
        return (y-x)/2;
    }

5.10.4 完善解法

上述的解法将最关键的,最核心的思路已经解决了,接下来我们要解决是最小索引的问题

  • 首先明确,遇到这种题目要求,解决方式就是修改有序数据结构的排序方式,我们希望取到的last,其start是比较小的。于是修改
        pq = new TreeSet<>((a,b)->{
           //算出两个线段的长度
           int distA =  distance(a);
           int distB = distance(b);
           //长度更长的更大,排后面
            if(distA == distB){
                return b[0] - a[0];
            }
            return distA-distB;
        });
  • 除此之外,还要改变distance函数,不能让它简单地计算一条线段两个端点间的长度,而是让它计算该线段重点和端点之间的长度
    //计算线段的长度
    private int distance(int[] intv){
        int x = intv[0];
        int y = intv[1];
        if(x == -1){
            return y;
        }
        if(y == this.n){
            return this.n-(x+1);
        }
        //中点和端点之间的长度
        return (y-x)/2;
    }

相关文章:

  • 基于retas的动漫动画制作与设计
  • 【Lua 入门基础篇(十)】文件I/O
  • Git 详细教程之五:SSH 免密登陆 GitHub
  • 单元测试啊
  • DolphinScheduler实例表备份、清理
  • java基于springboot+vue+elementui的校园志愿者活动管理系统
  • win7系统的两种硬盘格式mbr和gpt怎么选择?
  • (附源码)springboot课程在线考试系统 毕业设计 655127
  • Java网络教程之Socket
  • 关于Spring-Boot配置加载顺序解读
  • 小白量化《穿云箭集群量化》(3)量化策略编写(2)
  • 【HCSD零代码云上开发】零代码入门微信小程序和物联网
  • 博士生做科研想 idea 发现早就有人做过了,该怎么调整心态?
  • 嵌入式 - 瑞萨宣讲
  • 【云原生 | Kubernetes 系列】---Prometheus监控mysql
  • 【391天】每日项目总结系列128(2018.03.03)
  • Android开源项目规范总结
  • Codepen 每日精选(2018-3-25)
  • flutter的key在widget list的作用以及必要性
  • JavaScript 基本功--面试宝典
  • LintCode 31. partitionArray 数组划分
  • Mysql5.6主从复制
  • Python3爬取英雄联盟英雄皮肤大图
  • React-Native - 收藏集 - 掘金
  • SegmentFault 社区上线小程序开发频道,助力小程序开发者生态
  • Service Worker
  • Windows Containers 大冒险: 容器网络
  • 机器学习学习笔记一
  • 看图轻松理解数据结构与算法系列(基于数组的栈)
  • 聊聊hikari连接池的leakDetectionThreshold
  • 排序算法学习笔记
  • 数据可视化之 Sankey 桑基图的实现
  • 通过几道题目学习二叉搜索树
  • 直播平台建设千万不要忘记流媒体服务器的存在 ...
  • ​​​​​​​​​​​​​​Γ函数
  • # 达梦数据库知识点
  • #DBA杂记1
  • #LLM入门|Prompt#3.3_存储_Memory
  • (2)(2.4) TerraRanger Tower/Tower EVO(360度)
  • (8)STL算法之替换
  • (pojstep1.1.1)poj 1298(直叙式模拟)
  • (附源码)springboot家庭装修管理系统 毕业设计 613205
  • (附源码)springboot金融新闻信息服务系统 毕业设计651450
  • (附源码)springboot社区居家养老互助服务管理平台 毕业设计 062027
  • (附源码)计算机毕业设计高校学生选课系统
  • (简单) HDU 2612 Find a way,BFS。
  • (三维重建学习)已有位姿放入colmap和3D Gaussian Splatting训练
  • (十)T检验-第一部分
  • (使用vite搭建vue3项目(vite + vue3 + vue router + pinia + element plus))
  • (转)真正的中国天气api接口xml,json(求加精) ...
  • .form文件_SSM框架文件上传篇
  • .NET Framework 3.5中序列化成JSON数据及JSON数据的反序列化,以及jQuery的调用JSON
  • .NET Framework杂记
  • .NET 使用 ILMerge 合并多个程序集,避免引入额外的依赖
  • .NET/C# 使用反射注册事件