【问题标题】:Interview Scheduling Algorithm面试调度算法
【发布时间】:2015-06-03 00:38:05
【问题描述】:

我试图想出一种算法,它总是在最好的时间内产生这个问题的最佳解决方案:

n 的职位候选人和k 房间,他们在一天中的不同时间安排了面试。每个房间的面试都有特定的时间表,每个面试都有指定的开始时间(si),结束时间(fi)和面试室(r)。所有时间单位总是整数。此外,我们需要安排全天与当前正在接受采访的人的合影。这些照片实际上并不需要任何时间,但在一天中的某个时间点,每个受访者都必须在照片中。如果我们在t 的时间安排一张照片,那么目前正在接受采访的所有人都会在这张照片中。拍照对其余每个采访的开始和结束时间没有影响。所以问题是这样的:一个无序列表的采访,每个都有变量(si,fi,ri),怎么办您确保每个面试候选人都在照片中,同时尽可能少地拍照?

所以理想情况下,我们会在尽可能多的人在场时拍照,以尽量减少拍照的数量。我最初的想法是一种蛮力,但这将是一个非常糟糕的 big-O 运行时。在返回尽可能少的照片的同时最小化该算法的运行时间是非常重要的。话虽如此,如果您能想到一个不能完美解决问题的快速贪心算法,我也想听听。

我确信我在这里的描述远非完美,所以如果您希望我澄清任何事情,请随时发表评论,我会回复您。

【问题讨论】:

  • 澄清一下,当你拍照时,它包括所有房间的所有受访者?如果是,房间信息是否会以任何方式影响生成的解决方案?
  • @phari 是的。所有房间的所有受访者都在照片中。房间信息在那里,因为有时房间里会有人,有时不会。一个房间不能超过 1 人。因此,如果我们有 5 个房间,那么最多有 5 个人被采访,但可能有 0 个。这是否回答了您的问题?

标签: algorithm dynamic-programming greedy divide-and-conquer


【解决方案1】:

从以下观察开始:

  1. 每次采访必须至少拍摄一张照片,因为我们无法在受访者到达之前或离开之后拍摄他们。
  2. 可供拍照的一组人仅在时间 si 和 fi 发生变化。
  3. 在一个到达事件si之后,如果下一个事件j是一个到达,si和sj,因为在 si 上可用的每个人都在 sj 上可用。
  4. 因此,您可以通过到达事件(最多 k 个)让可用的受访者集合“积累”,然后等待拍照,直到有人即将离开。

因此我认为以下算法应该有效:

  1. 将到达和离开时间放入一个列表并对其进行排序(时间应保持标记为“到达”或“离开”以及受访者的索引)。
  2. 创建一个大小为n 的布尔数组A,以跟踪每个受访者是否有空(面试正在进行中)。
  3. 创建一个大小为n 的布尔数组P,以跟踪是否每个受访者都被拍照。
  4. 循环排序的时间列表(索引变量i):

    一个。如果遇到到达,请将A[i] 设置为true

    b.如果遇到离开j,请检查P[j] 以查看离开的人是否已被拍照。如果没有,请立即拍照并记录其效果(对于所有A[k] = true 设置P[k] = true)。最后将A[i]设置为false

排序是 O(n log n),循环有 2n 迭代,检查数组是 O(1)。但是由于在每个拍照事件中,您可能需要循环遍历A,因此在最坏的情况下,总体运行时间是 O(n2)(如果没有采访重叠,就会发生这种情况)时间)。

【讨论】:

    【解决方案2】:

    这是O(n log n) 解决方案:

    Step 1:将所有采访的开始时间和结束时间分别排序,但同时跟踪它们被排序到的位置(即原始索引和排序后的索引)。这导致下面有 4 个数组

    • sst[] (sst = 排序开始时间)

    • sft[] (sft = 排序完成时间)

    • sst2orig[](sst 索引到原始索引)

    • sft2orig[](sst 索引到原始索引)

      注意:根据上述 4 个数组的定义, “sst2orig[j] = i & sst2orig[k] = i”表示 interview [i] 有开始时间sst[j] 和结束时间sft[k]

    步骤 2:定义一个布尔数组 p_taken[] 来表示面试的候选人是否已经被拍照。数组中的所有元素最初都会设置为 false。

    第 3 步:循环

    std::vector<int> photo_time;
    int last_p_not_taken_sst_index = 0;
    for (int i=0; i<sft.size; i++) {
      // ignore the candidate already photographed
      if (p_taken[sft2orig[sft[i]]]) continue;
        
      // Now we found the first leaving candidate not phtographed, we
      // must take a photo now.
      photo_time.push_back(sft[i]);
        
      // So we can now mark all candidate having prior sst[] time as
      // already photographed.  So, we search for the first elm. in
      // sst[] that is greater than sft[i], and returns the index.   
      // If all elm. in sst[] is smaller than sft[i], we return sst.size().
      // This could be done via a binary search
      int k = upper_inequal_bound_index(sst, sft[i]);
      
      // now we can mark all candidate with starting time prior than sst[k]
      // to be "photographed".  This will include the one corresponding to
      // sft[i]
      for (int j=last_p_not_taken_sst_index; j<k; j++)
         p_taken[sst2orig[j]] = true;
      last_p_not_taken_sst_index = k;
    }
    

    最终答案保存在photo_time中,照片数量为photo_time.size()。

    时间复杂度:

    第 1 步:排序:O(n log n)

    第二步:初始化 p_taken[]: O(n)

    第 3 步:我们循环 n 次,并且在每个循环中

    3-1 检查 p_taken: O(1)

    3-2 二分查找:O(log n)

    3-3 标记候选者:聚合 O(n),因为我们只标记一次,每个候选者。

    所以,对于第 3 步,总体而言:O(n x ( 1 + log n) + n) = O(n log n)

    第1~3步,总计:O(n log n)

    请注意,第 3 步可以进一步优化:我们可以缩小以排除那些已经在之前的二分搜索范围。但最坏的情况仍然是每个循环 O(log n)。因此总数仍然是 O(n log n)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-16
      相关资源
      最近更新 更多