【问题标题】:Time Limit Exceeded for Merge k Sorted Lists(leetcode)合并 k 个排序列表超出时间限制(leetcode)
【发布时间】:2015-03-09 05:17:02
【问题描述】:

merge-k-sorted-lists

合并 k 个排序的链表并将其作为一个排序列表返回。分析并描述其复杂性。

我的代码:

    ListNode *mergeTwoLists(ListNode *p1, ListNode *p2) {
    ListNode dummy(-1);
    ListNode *head = &dummy;
    while(p1 != nullptr && p2 != nullptr) {
        if (p1->val < p2->val) {
            head->next = p1;
            head = head->next;
            p1 = p1->next;
        } else {
            head->next = p2;
            head = head->next;
            p2 = p2->next;
        }
    }
    if (p1 != nullptr) {
        head->next = p1;
    }
    if (p2 != nullptr) {
        head->next = p2;
    }
    //head->next = nullptr;
    return dummy.next;
}
ListNode *mergeKLists(vector<ListNode *> &lists) {
    if (lists.size() == 0) return nullptr;
    if (lists.size() == 1) return lists[0];
    ListNode *p1, *p2, *p;
    while (lists.size() > 1) {
        p1 = lists.back();
        lists.pop_back();
        p2 = lists.back();
        lists.pop_back();
        p = mergeTwoLists(p1, p2);
        lists.push_back(p);
    }
    return lists[0];
}

我总是超过时间限制。我应该如何更改程序?

【问题讨论】:

  • 这是一个标准问题,您只需 google 即可获得答案。另外我怀疑这已经在stackoverflow上问过了,所以你也可以在这里搜索。我仍然给出了广泛的想法,希望它有所帮助。
  • @sasha 谢谢。我使用lists.insert(lists.begin(), p) 更改我的代码。之前在stackoverflow中搜索过,可能是我粗心,我会努力改掉那个坏习惯。谢谢!

标签: c++ algorithm sorting


【解决方案1】:

您正在做的事情很复杂O(nk^2) 其中n 是每个数组的大小。您一次合并两个列表。为什么 ?您合并前两个列表需要2n 操作,前两个组合的大小为2n。现在将它与第三个合并,数组大小变为3n3n 操作已完成,因此操作总数为2n+3n+....kn(算术级数),即O(nk^2)。取而代之的是优先队列( min heap )插入所有 k 列表的第一个元素。现在每次从优先级队列中取出最小的元素(将其放入新列表中),将其从优先级队列中删除并插入该元素所属的列表中的下一个元素。由于所有元素都从优先级队列中插入和删除一次,总共有nk 个元素,复杂度为O(nklog(k))。 (删除/插入时间)优先队列为O(log(number_of_elements_in_queue))。并且在队列中随时最多有k 个元素。
有关更详细的说明和代码,请查看此处:Merging k sorted lists。我认为这足以在 leetcode 上获得 AC :)。

【讨论】:

    【解决方案2】:

    您的问题是您正在进行不平衡的合并。如果每个列表有 n 个元素开头,merge(a,b) 表示您合并长度为ab 的列表(这需要时间O(a+b)),那么您正在执行的操作是

    merge(n,n)
    merge(2n,n)
    merge(3n,n)
    merge(4n,n)
    ....
    

    因此,您在长长的列表中多次迭代需要付出大量成本;使用k 元素,你正在做关于(1/2) k^2 n 的工作。

    您可以寻找一种专门的不平衡合并算法,但更简单的方法是重新组织您的工作以合并大小相似的列表。如果您从 k 开始列出每个 n 元素,那么您会这样做

    k/2 instances of `merge(n,n)`
    k/4 instances of `merge(2n,2n)`
    ...
    1 instance of `merge(nk/2, nk/2)`
    

    每个步骤花费nk 时间,并且有lg(k) 步骤,总成本为nk lg(k)

    如果k 不是 2 的幂或者列表的长度不同,您可以做很多事情来尽量减少总工作量,但一个非常简单的方法是lists deque 而不是 vector,并且对于每次合并,您都会弹出两个后面的列表并将结果推送到 front 而不是 back .另一个简单的优化是首先按长度对列表进行排序。


    k 不太大时,other answer 可能会更好。当k 相当大时,您可能最好使用混合算法:您选择一个合适的m,然后按照我的描述组织全部工作,但不是一次合并两个列表,而是合并@ 987654342@ 一次列出。

    我对合适的m 的前两个猜测是ceil(sqrt(k)) 和另一个答案的算法对于m-way 合并有效的最大值。

    (如果由于某种奇怪的原因m 仍然很大,那么你用混合算法进行m-way 合并)


    我为什么要做出上述预测?另一个答案只让一个数据通过,所以只要你的 CPU 可以有效地维护一个长度为 k 的优先级队列以及同时从 k 列表中读取,它肯定比我的算法更好对数据进行多次传递。

    但是当k 变得太大时,你就会遇到问题:

    • 您的 TLB 可能没有足够的条目一次从 k 列表中读取
    • 您的缓存可能不够大,无法存储所有k 列表中的一两个缓存行,也无法容纳优先级队列

    缓存未命中,尤其是 TLB 未命中会降低性能。混合算法重新组织工作,以便您保持我的算法方法(平衡合并)的好处,而几乎所有工作都是通过另一个答案的有效m-way 合并完成的。

    【讨论】:

      猜你喜欢
      • 2022-08-16
      • 2022-11-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-03
      • 1970-01-01
      • 2019-03-20
      • 2022-08-24
      相关资源
      最近更新 更多