在 O(logk) 时间内删除 K 个排序的双向链表的最小值

Posted

技术标签:

【中文标题】在 O(logk) 时间内删除 K 个排序的双向链表的最小值【英文标题】:Remove min for K sorted doubly linked lists in O(logk) time 【发布时间】:2020-07-12 17:12:39 【问题描述】:

所以我正在尝试创建一个算法来解决从 k 个排序的双向链表中删除最小值的问题。在这里,我们有 k 个排序的双向链表,其中 n 是所有列表中所有元素的总和。我们希望能够在 O(logk) 时间内移除最小值,并且只需要 O(n)是时候初始化数据结构了。

我正在考虑创建一个最小堆,其中元素是每个列表的第一个元素(因此一次只有 k 个元素在堆中),但我不完全确定从那里去哪里,特别是添加其余元素放入堆中。谁能帮我解决这个问题?

【问题讨论】:

O(log n) 时间甚至不足以遍历您拥有的链表。但除此之外,是的,将事物扔进以列表中的最小值为键的二进制堆中是个好主意。 啊等一下,我意识到我打错了,假设是 O(n) 时间来初始化数据结构。关于如何考虑列表中不在堆中的其他元素,您有什么建议吗?例如,假设我有 2 个列表 A = 1, 1, 2, 3 和 B = 2, 3, 4, 5。堆将包含元素 1, 2,如果您调用 remove min 一次,将返回 1。如果你再次调用它,那么将返回 2,但它应该是 1,因为 A 有一个重复项。我不知道我该怎么做 @oliviah 堆将是一个链表堆,具有比较前导值的比较操作。现在您编写一个函数,从堆中拉出具有最小元素的列表,将该列表的尾部插入堆中(假设尾部非空),然后返回提取的值。 你知道每个列表的长度还是必须迭代才能知道? 【参考方案1】:

据我了解(如果我错了,请纠正我),您有 K 个已排序的双向链表。您想继续删除最小的元素。每次删除都必须在 O(logK) 内完成。

举个例子:

A = 1, 1, 2, 3
B = 2, 3, 4, 5

您可以将每个列表的第一个元素及其所属的列表插入到最小堆中。

heap = [key: 1, list: A, key: 2, list: B]

然后你可以从最小堆中弹出最小的元素。您现在可以删除此元素。

min_val = heap.pop()     // key: 1, list: A
min_list = min_val.list  // list = A
min_list.delete_first()  // new A = 1, 2, 3

删除后,您必须将此列表中的下一个元素添加回最小堆(如果列表非空,则跳过此步骤)。

heap.add( 
  key: min_list.fist_element(), // key: 1
  list: min_list                // list: A
)
// new heap = [key: 1, list: A, key: 2, list: B]

现在您可以对每个删除操作重复此操作。从最小堆中弹出最小的O(1),删除最小的元素(即双向链表中的第一个元素)O(1),添加下一个同一列表中的元素返回到最小堆 O(logK)

【讨论】:

如果你的堆实现允许,我会避免弹出最小的元素:我会用列表的新头部替换它的值,并根据需要将它下推到堆中——节省一个堆操作。 【参考方案2】:

您有 k 个列表,所有列表的总和为 n,因此每个列表的平均总和应为 a = n/k

秘密问题是:您需要多长时间遍历一个列表才能找出总和至少为 a

例如,假设 a = 10 并且您的列表是 [5, 6, 7]。只需查看第一个元素(并知道列表的长度),您就知道列表的总和大于平均值,因此您可以完全忽略它。基本上,对于您从列表L 中读取的每个元素Li,您可以询问是否

L1 + L2 + ... + Li + (Li * (s - i)) >= n/k

其中s 是列表的长度。只要您需要阅读列表,这不会花费您额外的时间,并且会减少大约一半的列表。

【讨论】:

以上是关于在 O(logk) 时间内删除 K 个排序的双向链表的最小值的主要内容,如果未能解决你的问题,请参考以下文章

c_cpp 合并k排序列表此解决方案不具有时间效率,最佳时间复杂度为o(n * logk),这一个是o(n * k)http://oj.leetcode.c

寻找最小的k个数(大顶堆方法)

23. 合并K个排序链表 分治

为啥使用双向链表删除哈希表的元素是 O(1)?

桶排序计数排序

剑指offer题目系列三(链表相关题目)