【问题标题】:Merging two sorted singly linked list合并两个排序的单链表
【发布时间】:2017-08-23 02:41:24
【问题描述】:

我在找一些考试练习时在 Leetcode 上遇到了这个问题,我想出了以下解决方案:

public class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
}

public class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        // base case: if any one of the lists are empty then we are done. 
        if (l1 == null) return l2;
        else if (l2 == null) return l1;

        ListNode head = new ListNode(-1); 
        // dummy head node

        ListNode prev = head; // pointer to do the modifications on the list
        while ((l1 != null) && (l2 != null)) {
            // while both lists arent empty
            int val;
            if (l1.val < l2.val) {
                val = l1.val;
                l1 = l1.next;
            } else {
                val = l2.val;
                l2 = l2.next;
            }
            ListNode curr = new ListNode(val); // creates a new node with the chosen value
            prev.next = curr; // update pointers
            prev = curr;
        }

        // one of the list is finished. we add the rest onto the list ln
        if (l1 == null) prev.next = l2;
        else prev.next = l1;

        return head.next;
    }
}

它似乎通过了所有测试 - 但是我遇到的问题是它比提交的解决方案的 90% 慢。我最近学习了链表,但我仍然对这个概念并不完全满意,这就是为什么在理解上可能存在一些失误,导致这里的代码效率低下。如果有人能解释如何改进我的实现,我将不胜感激。

【问题讨论】:

  • 为什么在方法的最后只添加剩余的节点时要创建新节点?看来您不必创建新节点,只需(重新)使用传递的节点应该会给您带来相当多的性能改进,尤其是对于长列表。
  • 怎么会有人知道为什么你的代码比其他一些未知的代码要慢,而这些代码是一些未指定问题的解决方案?
  • null 列表!= 空列表。检查列表的size() 及其空值
  • @pvg 好吧,恐怕这是我能得到的最具体的了。这是原来的问题:leetcode.com/problems/merge-two-sorted-lists
  • 你只是设法得到更具体的,也许不是。

标签: java algorithm merge linked-list


【解决方案1】:

您的代码有两个问题。

首先,它与输入数据不一致。如果输入列表之一为空,则例程返回另一个输入列表作为结果。另一方面,如果两个输入列表都不为空,则例程会创建 新项目 以构建结果列表。但是,当一个输入列表在合并过程中耗尽时,另一个的剩余部分将附加到结果列表中。最终,结果列表部分由 新节点 和输入列表之一的尾随部分构建。

这会导致创建节点副本和销毁原始节点的不必要开销,这些原始节点已被其副本替换。

为避免这种情况,请直接使用从输入列表中获取的节点来构建结果列表。然后不创建新节点,不回收旧节点,在复制之前没有中间变量来保留 val,如果 ListNode 类被扩展,你不会忘记复制其他数据成员的风险......

另一个是对输出节点进行排序。您使用“小于”运算符来决定是从l1 还是从l2 获取下一个节点。那是错误的。您应该使用“小于或等于”,因此对于相等键,您首先从l1 获取节点,然后从l2 获取节点。这样,具有相同键值val 但在其他方面可区分的节点(例如,它们具有其他数据成员)保持原始顺序。我们称之为稳定排序。

实施:

public ListNode mergeTwoLists(ListNode l1, ListNode l2) {

    ListNode head = new ListNode(-1);  // temporary head node
    ListNode last = head; // the last item of the list; we append here

    while ((l1 != null) && (l2 != null)) { // while both lists arent empty
        if (l1.val <= l2.val) {            // 'or equal' for stable merging
            last.next = l1;                // append the chosen node to result list
            l1 = l1.next;                  // skip (effectively remove) it in input list
        } else {
            last.next = l2;
            l2 = l2.next;
        }
        last = last.next;                  // the appended node becomes last
    }

    // one of the lists is finished - append the rest of the other one
    if (l1 == null) last.next = l2;
    else last.next = l1;

    return head.next;
}

此外,我们不需要测试任何输入列表是否为空 - 如果是,则跳过 while 循环而不进行迭代,最后一个 if 将非空输入列表附加到结果中。这里的收益比 if 少一个,但代价是创建了一个临时的 head 节点。

我们也可以以额外的条件分支为代价摆脱临时的ListNode 变量。当节点类变得更复杂时,这可能很有用,因为我们可能需要为构造函数提供更多参数,而这些参数在合并例程中可能不可用。

public ListNode mergeTwoLists(ListNode l1, ListNode l2) {

    if (l1 == null) return l2;
    else if (l2 == null) return l1;    // make sure both lists aren't empty

    ListNode head = null;                  // the resulting list's head

    if (l1.val <= l2.val) {                // choose the head node
        head = l1;
        l1 = l1.next;
    } else {
        head = l2;
        l2 = l2.next;
    }

    ListNode last = head;  // the last item of the list; we append here

    while ((l1 != null) && (l2 != null)) { // while both lists arent empty
        if (l1.val <= l2.val) {            // 'or equal' for stable merging
            last.next = l1;                // append the chosen node to result list
            l1 = l1.next;                  // skip (effectively remove) it in input list
        } else {
            last.next = l2;
            l2 = l2.next;
        }
        last = last.next;                  // the appended node becomes last
    }

    // one of the lists is finished - append the rest of the other one
    if (l1 == null) last.next = l2;
    else last.next = l1;

    return head;
}

【讨论】:

  • 这不是回答问题,而是对练习的回答。
  • @pvg 没有回答? OP 询问“是否有人可以解释如何改进我的实施”。好吧,这是一个示例,如何改进它们的实现:避免创建不必要的对象并重新链接输入数据;完全按照任务描述:不是“使用合并数据创建新列表”而是“合并输入列表”(他们说:“The new list should be made by splicing together the nodes of the first two lists.”)。
  • 它没有解释任何事情,它只是放弃了一个实现,没有说明它比海报的实现更好,为什么性能特征不同等等。它只是一个代码块。这是一个糟糕的答案,就像“为什么这段代码不起作用”是一个可标记的糟糕问题一样。
  • @pvg 也许 CiaPan 可以通过添加更多说明来改进答案,但是我个人发现这个答案在消化代码后很有帮助 - 我想这有助于我找到和理解推理的目标支持更好的实施。
  • @user2938375 这很好,但该网站的目的是收集许多人认为有用的问题和答案,而不是为您个人充分回答您的具体问题。这就是为什么它有关于什么是好的问题和答案的指南。
猜你喜欢
  • 2023-03-25
  • 2011-01-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多