日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

调度考生的座位

發布時間:2024/4/11 编程问答 55 豆豆
生活随笔 收集整理的這篇文章主要介紹了 调度考生的座位 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

調度考生的座位

文章目錄

    • 調度考生的座位
    • 一、問題描述
    • 二、分析
    • 三、代碼
    • 四、進階問題
    • 五、最后總結

一、問題描述

先來描述一下題目:假設有一個考場,考場有一排共 N 個座位,索引分別是 [0..N-1],考生會陸續進入考場考試,并且可能在任何時候離開考場。

你作為考官,要安排考生們的座位,滿足:每當一個學生進入時,你需要最大化他和最近其他人的距離;如果有多個這樣的座位,安排到他到索引最小的那個座位。這很符合實際情況對吧,

也就是請你實現下面這樣一個類:

class ExamRoom {// 構造函數,傳入座位總數 Npublic ExamRoom(int N);// 來了一名考生,返回你給他分配的座位public int seat();// 坐在 p 位置的考生離開了// 可以認為 p 位置一定坐有考生public void leave(int p); }
  • 比方說考場有 5 個座位,分別是 [0…4]:
  • 第一名考生進入時(調用 seat()),坐在任何位置都行,但是要給他安排索引最小的位置,也就是返回位置 0。
  • 第二名學生進入時(再調用 seat()),要和旁邊的人距離最遠,也就是返回位置 4。
  • 第三名學生進入時,要和旁邊的人距離最遠,應該做到中間,也就是座位 2。
  • 如果再進一名學生,他可以坐在座位 1 或者 3,取較小的索引 1。
  • 以此類推。
  • 剛才所說的情況,沒有調用 leave 函數,不過讀者肯定能夠發現規律:
  • 如果將每兩個相鄰的考生看做線段的兩端點,新安排考生就是找最長的線段,然后讓該考生在中間把這個線段「二分」,中點就是給他分配的座位leave(p) 其實就是去除端點 p,使得相鄰兩個線段合并為一個。
  • 核心思路很簡單對吧,所以這個問題實際上實在考察你對數據結構的理解。對于上述這個邏輯,你用什么數據結構來實現呢?

二、分析

  • 根據上述思路,首先需要把坐在教室的學生抽象成線段,我們可以簡單的用一個大小為 2 的數組表示。
  • 另外,思路需要我們找到「最長」的線段,還需要去除線段,增加線段。
  • 但凡遇到在動態過程中取最值的要求,肯定要使用有序數據結構,我們常用的數據結構就是二叉堆和平衡二叉搜索樹了。
  • 二叉堆實現的優先級隊列取最值的時間復雜度是 O(logN),但是只能刪除最大值。平衡二叉樹也可以取最值,也可以修改、刪除任意一個值,而且時間復雜度都是 O(logN)。
  • 綜上,二叉堆不能滿足 leave 操作,應該使用平衡二叉樹。所以這里我們會用到 一種數據結構 TreeSet,這是一種有序數據結構,底層由紅黑樹維護有序性。

首先,如果有多個可選座位,需要選擇索引最小的座位對吧?我們先簡化一下問題,暫時不管這個要求,實現上述思路。

  • 這個問題還用到一個常用的編程技巧,就是使用一個「虛擬線段」讓算法正確啟動,這就和鏈表相關的算法需要「虛擬頭結點」一個道理
// 將端點 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<>((a, b) -> {// 算出兩個線段的長度int distA = distance(a);int distB = distance(b);// 長度更長的更大,排后面return distA - distB;});// 在有序集合中先放一個虛擬線段addInterval(new int[] {-1, N}); }/* 去除一個線段 */ 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) {return intv[1] - intv[0] - 1; }

「虛擬線段」其實就是為了將所有座位表示為一個線段:

三、代碼

有了上述鋪墊,主要 API seat 和 leave 就可以寫了:

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); }

  • 至此,算法就基本實現了,代碼雖多,但思路很簡單:找最長的線段,從中間分隔成兩段,中點就是 seat() 的返回值;找 p 的左右線段,合并成一個線段,這就是 leave(p) 的邏輯

四、進階問題

但是,題目要求多個選擇時選擇索引最小的那個座位,我們剛才忽略了這個問題。比如下面這種情況會出錯:

  • 現在有序集合里有線段 [0,4] 和 [4,9],那么最長線段 longest 就是后者,按照 seat 的邏輯,就會分割 [4,9],也就是返回座位 6。但正確答案應該是座位 2,因為 2 和 6 都滿足最大化相鄰考生距離的條件,二者應該取較小的。
  • 遇到題目的這種要求,解決方式就是修改有序數據結構的排序方式。具體到這個問題,就是修改 TreeMap 的比較函數邏輯
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 == N) return N - 1 - x;// 中點和端點之間的長度return (y - x) / 2; }

  • 這樣,[0,4] 和 [4,9] 的 distance 值就相等了,算法會比較二者的索引,取較小的線段進行分割。到這里,這道算法題目算是完全解決了。

五、最后總結

  • 這個問題其實并不算難,雖然看起來代碼很多。核心問題就是考察有序數據結構的理解和使用。
  • 處理動態問題一般都會用到有序數據結構,比如平衡二叉搜索樹和二叉堆,二者的時間復雜度差不多,但前者支持的操作更多。
  • 既然平衡二叉搜索樹這么好用,還用二叉堆干嘛呢?因為二叉堆底層就是數組,實現簡單啊。
  • 你實現個紅黑樹試試?操作復雜,而且消耗的空間相對來說會多一些。具體問題,還是要選擇恰當的數據結構來解決。

總結

以上是生活随笔為你收集整理的调度考生的座位的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。