【问题标题】:Job Interview Algorithm?求职面试算法?
【发布时间】:2020-11-07 15:31:39
【问题描述】:

更新:请有人回答我对 n 的最后评论。 '代词' m.'s answer?

请注意:我之前曾问过这个问题,但它完全是一团糟,所以我用更多细节和原始形式写了它。

问题:

我正在管理 N 个参与者(每个参与者的索引从 1 到 N)之间的投票系统,我希望支持以下功能:

  1. Init(N) - 初始化数据结构。 -O(1)
  2. Vote(j,i) - 将人 j 投票的结果表(正好 1)添加到人 i - 不允许某人自己投票。 -O(1)
  3. Voters(i) - 返回投票给 i 的人数。 -O(1)
  4. Origin(j) - 返回 j 给其他人的投票数。 -O(1)
  5. Favored(k) - 根据获得的票数打印排名靠前的参与者(按降序排列)。 -O(k)
  6. Avoided() - 打印所有未获得任何投票的参与者。 -O(r) 其中 r 是打印参与者的数量

在这个问题中,空间复杂度应该是O(N)

只允许使用数组和(双)链表。


我做了什么? 我只需声明一个大小为 N 且每个单元格包含值的数组即可轻松解决 1-4; gotsent。当i 投票给j 时,我增加了j 的值并将i 的值加一。

我仍然不知道如何以所需的复杂性解决 5 和 6。

注意:我正在寻找算法/想法,而不是实际代码。

【问题讨论】:

  • 您的实际问题是什么?您还需要提供与问题相关的代码,更具体地说明您的问题并阅读How do I ask a good question?
  • @pzaenger 谢谢你,我已经做了必要的改变。如果有任何遗漏,请告诉我
  • 地图、集合和优先级队列的组合应该可以解决问题。
  • @nice_dev 如前所述:只允许使用数组和(双)链表。
  • 也许我可以利用这样一个事实,即当有人投票时,他每次都会投 一个 票?

标签: arrays algorithm data-structures linked-list structure


【解决方案1】:

请注意,对于每个操作,被投票的候选人的得分恰好增加了 1。

这开启了一个新策略 - 与其将候选人映射到其分数,不如将分数映射到具有该分数的候选人列表。

这可以很简单地实现为候选列表:(在类似模板的语法中:list<list<Candidate>>)。

此外,保留一个数组,将每个候选编号映射到实际Candidate元素的指针。

带有 0 的候选者列表将被隐式设置为最初在 similar way you initialize an array in O(1) 中的所有候选者。

  • 投票时:
  1. 你从参考文献中找到候选人:O(1)
  2. 您将其从当前列表中删除,然后将其添加到下一个列表中:O(1)
  3. 为了支持O(r)中的Avoided():如果“0”列表中的元素个数小于一半,则改为常规列表。
  4. 如果表示分数的前一个元素现在没有候选者,则删除它,并将前一个分数直接链接到下一个分数(即,如果没有分数为 3 的候选者,则连接2<->4)这确保了O(n) 空间到期没有太多的空列表节点。
  • 现在通过从头到尾迭代分数列表(在输出 k 候选人后停止),在 O(k) 中轻松获得 topK 并完成
  • Avoided 现在是 O(n) = O(r) 如果超过一半的候选人被避免,或者 O(r) 否则感谢插入优化 (3)。

【讨论】:

  • @orlp 不完全是。您可以跳过其中包含 0 个候选项的元素,直接跳到下一个分数。最多可以存在n 分数,因此这符合标准。我会详细说明。
  • 实际上,我忽略了您有一个列表列表 - 不是向量,没关系。这和我写的想法是一样的,但我是用侵入性列表来做的。
  • “通过从头到尾迭代分数列表,现在很容易获得 topK,并且在 O(k) 中完成”我如何从最后迭代链表?另外,在这种情况下,我需要双向链表吗?
  • 另一个问题,我怎么知道给定的链表代表了多少票?
  • #3 不清楚。将数组更改为列表是 O(N) 操作。什么时候执行?
【解决方案2】:

这是实现 Avoided() 的另一种方法。将两个数字与每个已投票的人相关联,即开始运行和运行结束。最初所有元素都设置为None(可以使用 O(1) 数组初始化技巧来完成)。

m 人第一次被投票时,更新startOfRunendOfRun 数组:

if startOfRun[m-1] != None and startOfRun[m+1] == None
   endOfRun[startOfRun[m-1]] = m
else if startOfRun[m-1] == None and startOfRun[m+1] != None
   startOfRun[endOfRun[m+1]]
else if startOfRun[m-1] != None and startOfRun[m+1] != None
   endOfRun[startOfRun[m-1]] = endOfRun[m+1]
   startOfRun[endOfRun[m+1]] = startOfRun[m-1]
else
   startOfRun[m] = m
   endOfRun[m] = m

(为简洁起见,省略了边缘条件)。

现在您已经获得了投票支持的人的运行情况,并且您可以轻松地从每次运行的开始到结束。运行中的数字都是错误的,但我们不在乎它们。有 O(r) 次运行,因此您可以跳过所有在 O(r) 中投票的人。


这是实现 Favored() 的另一种方法。有两个数组,(1)按分数排序的扩展数组,以及(2)从分数到第一个数组中分数不低于该分数的最后一个人的索引的映射(如果没有这样的人,则@ 987654326@)。最初第一个数组为空,第二个包含Nones。示例:

(array 1)
(index)       1 2 3 4 5 6 7 8 9
person        3 6 5 1 4 2 8 9 7
score         7 7 7 5 5 3 2 2 2

(array 2)
score         1 2 3 4 5 6 7 8 9
index in 1st  9 9 6 5 5 3 3 - -

当一个人第一次被投票时,将其添加到得分为 1 的数组末尾,并更新array2[1]。一旦该人再次被投票,它与数组中具有相同分数的第一个人交换,分数增加,并更新第二个数组(我们只需要更新一个元素,对应于新的分数)。

【讨论】:

  • 如果您将附加值与每个参与者相关联,则初始化不是 O(1)。
  • @Sneftel 他们最初都没有。
  • 对,一个需要将每个元素单独置空的操作。
  • @Sneftel 有一个技巧可以避免数组初始化的 O(n) 成本,因为空间价格增加了一个常数因子。其中一个答案中有一个链接。 OP 已经在使用它了。
  • @WhiteForce (1) 您使用数组作为映射(退化哈希映射)。 (2) 尝试绘制它。基本上,run 任一端的每个元素都知道另一端在哪里,因此当 run 扩展或两个 run 合并时,我们可以更新此信息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-05
  • 1970-01-01
  • 2021-09-15
  • 2010-12-16
  • 2015-06-03
  • 1970-01-01
相关资源
最近更新 更多