繁体   English   中英

将 3 个链表合并为 1 个(Java)

[英]Merging 3 linked lists into 1 (Java )

我有一个关于我正在参加的编码课程的最终审查的问题。 它要求将 3 个链表合并为 1 个链表。 我遇到的问题是在合并列表时,我能够按升序合并三个列表,但我缺少第二个列表 23 和 25 的最后 2 个节点。我不知道为什么它停在那里。 问题在这里:

编写一个名为 LinkedTest 的程序:

  1. 创建三个排序的整数单链表,如下所示
First List:  2 11 19 21 24

Second List: 14 15 18 23 25

Third List:  3 9 17 20 22
  1. 将三个链表合并成一个新的排序链表,如下所示: 2 3 9 11 14 15 17 18 19 20 21 22 23 24 25
  2. 返回新排序的链表要求:你的程序的时间复杂度必须小于或等于 O( nlog n )

这是我的代码:

public class LinkedTest {

public static class ListNode {

    private int data;
    ListNode next;

    public ListNode(int data) {
        this.data = data;
        next = null;
    }
}

ListNode head;

public static void main(String[] args) {

    LinkedTest list = new LinkedTest();

        int[] data1 = { 2, 11, 19, 21, 24 };

        ListNode head1 = new ListNode(data1[0]);

        for (int i = 1; i < data1.length; i++)
            list.push(head1, data1[i]);

        System.out.print("First List: ");
        list.display(head1);

        int[] data2 = { 14, 15, 18, 23, 25 };

        ListNode head2 = new ListNode(data2[0]);

        for (int count = 1; count < data2.length; count++)
            list.push(head2, data2[count]);

        System.out.println(" Second List: ") ;

        list.display(head2);

        int[] data3 = { 3, 9, 17, 20, 22 };

        ListNode head3 = new ListNode(data3[0]);

        for (int count = 1; count < data3.length; count++)
            list.push(head3, data3[count]);

        System.out.println(" Third List: ") ;

        list.display(head3);

        ListNode n = list.LinkedTest(head1, head2, head3);

        System.out.print(" Merged List: ");

        list.display(n);
    }



public ListNode LinkedTest(ListNode first, ListNode second, ListNode third) { 
      ListNode head = null;

        if (first == null && second != null && third != null)

            return second;

        else if (second == null && third != null && first != null)

            return third;

        else if (third == null && first != null && second != null)

            return first;

        else if (first.data < second.data && first.data < third.data) 
        {
            head = first;
            head.next = LinkedTest(first.next, second, third);
        } 
        else if (second.data < third.data && second.data < first.data)
        {
            head = second;
            head.next = LinkedTest(first, second.next, third);
        }

        else if (third.data < first.data && third.data < second.data)
        {
            head = third;
            head.next = LinkedTest(first, second, third.next);
        }

        return head;
    }

    public void push(ListNode head, int n) 
    {
        while (head.next != null)
            head = head.next;
        head.next = new ListNode(n);
    }

    public void display(ListNode head)
    {
        ListNode tempDisplay = head; 
        while (tempDisplay != null) 
        {
            System.out.print(tempDisplay.data);
            tempDisplay = tempDisplay.next; 
    }

    }
}

输出:

First List:   2 11 19 21 24 
Second List:  14 15 18 23 25 
Third List:   3 9 17 20 22 
Merged List:  2 3 9 11 14 15 17 18 19 20 21 22 24

为什么要限制自己进行 3 向合并? 让我们看一下 N 路合并的一般情况,然后将其应用于 3 路(或普通的无聊的 2 路)。

// drop-in replacement for your LinkedTest(), but I like the name better:
// ListNode n = list.merge(head1, head2, head3);
public ListNode merge(ListNode... nodes) {

    // find smallest, keep its index
    int firstIndex = -1;
    int firstValue = 0;
    for (int i=0; i<nodes.length; i++) {
        ListNode n = nodes[i];
        if (n != null && (firstIndex == -1 || n.data < firstValue)) {
            firstIndex = i;
            firstValue = n.data;
        }
    }

    if (firstIndex == -1) {
        // reached the end of all lists
        return null;
    } else {
        // use node with smallest as next head
        ListNode head = nodes[firstIndex];

        // call again with all lists, but skipping head
        // because we are already using it in the result list
        nodes[firstIndex] = head.next;
        head.next = merge(nodes);
        return head;
    }
}

您可以将其视为许多带有链接的链,链上带有数字,其中每条链的编号按升序排列。 要构建一个包含所有数字的大链,您需要:

  • 抓住剩余链子的所有小端
  • 选择最小的链接(如果没有剩余,则已完成)
  • 将该链接从其链上取下,并将其添加到新链的末尾
  • 回到选择旧链中最小的环节

在您的回答中,正如 Jacob_G 所指出的,当一个或多个列表为空时,您缺少一些选择正确最小元素的逻辑。 雅各布的回答很好,但我虽然我会向您展示更大的图景:如果您理解N3应该没有延伸(另外,您对总体思路有了深入了解)。

您将获得三个已排序的列表,并要求将它们合并为一个已排序的列表 实现这一点的算法非常简单。

我建议跟踪三个索引(每个输入列表一个)并将它们全部初始化为0 (每个列表的第一个元素)。 因为每个列表包含相同数量的元素,您可以简单地从0迭代到3n ,其中n列表s 之一的大小。

在循环内部,您希望使用各自的索引找到每个列表3个头元素之间的最小元素。 为此,只需查看元素并将它们相互比较即可。 找到最小值后,您可以将该元素附加到合并列表中并增加相应列表的索引(这很重要,因为不要两次附加相同的元素)。

您将重复此操作3n次(三个列表中的每个元素一次),结果将是一个按升序排序的合并列表

假设每个输入列表的大小总是相等,这个算法是O(3n) ,它被简化为O(n)

伪代码解决方案可能如下所示:

list1 = [2, 11, 19, 21, 24]
list2 = [14, 15, 18, 23, 25]
list3 = [3, 9, 17, 20, 22]
merged_list = []

indexes = [0, 0, 0]

for i in 0..15:
    minValue = Integer.MAX_VALUE
    minIndex = -1

    if indexes[0] < len(list1) and list1[indexes[0]] < minValue:
        minValue = list1[indexes[0]]
        minIndex = 0

    if indexes[1] < len(list2) and list2[indexes[1]] < minValue:
        minValue = list2[indexes[1]]
        minIndex = 1

    if indexes[2] < len(list3) and list3[indexes[2]] < minValue:
        minValue = list3[indexes[2]]
        minIndex = 2

    merged_list += minValue
    indexes[minIndex]++

你的问题是你没有写出所有的条件。 让我们看看您的用例。

第一个列表:2 11 19 21 24

第二个名单:14 15 18 23 25

第三个名单:3 9 17 20 22

根据您的代码逻辑,在第一次合并中,我们在first.data < second.data得到2 下一回合,我们在同一位置有 11 < 14。 但在那之后我们现在有
19 21 24
14 15 18 23 25
3 9 17 20 22
并且在接下来的回合中都不会满足任何条件。

我想你的代码是从合并两个列表的代码中修改而来的。 您应该回忆为什么每段代码都有效,并思考为什么您的代码没有按照您认为应该的方式工作。

让我们假设您的原始算法如下所示:

public ListNode LinkedTest(ListNode first, ListNode second) {

    ListNode head = null;

    if (first == null)

        return second;

    else if (second == null)

        return first;

    else if (first.data < second.data) 
    {
        head = first;
        head.next = LinkedTest(first.next, second);
    } 
    else if (second.data < first.data)
    {
        head = second;
        head.next = LinkedTest(first, second.next);
    }

    return head;
}

该方法的前半部分检查是否需要终止递归。 在这里,当两个列表之一到达末尾时,代码终止。 例如,如果您将列表[1, 3, 5, 7]与列表[2, 4, 6, 8, 9, 10]合并,则最终将列表[]与列表合并[8, 9, 10] 那是您需要通过返回列表并因此完成递归来终止的时候。

您对算法所做的修改是不正确的,因为在三向合并中,当第一个列表超出节点时,您不能简单地返回第二个列表。 终止条件变得更加复杂,您应该注意何时真正需要结束您的递归。 例如,如果您尝试合并[1, 2] , [3, 4] , [5, 6] ,假设算法的其他部分有效,那么您最终会在[] , [3, 4] , [5, 6] ,您将返回[3, 4]并丢弃[5, 6] ,这是不正确的。

一种解决方案是在任何一个列表出现时调用该算法的两个列表版本。 这需要更多的代码(这也是非常重复的),但是来自这个特定的代码更直接。 另一种解决方案是仅在两个列表为空时才终止,但这在递归情况下需要特别注意以检查空值。


该方法的后半部分比较列表的第一个元素,并使用较小的元素作为新列表的头部,然后将头节点的next数据成员分配给“无论合并其余列表的结果是什么” ”。 请注意,在此版本的代码中,每次只执行其中一个代码块。 (除非数据相等,否则会有bug。修复它留作练习。)只要不终止递归,我们就需要更深入,对吧!

您所做的修改不起作用,因为您将第一个数据与第二个数据进行比较,然后将第二个数据与第三个数据进行比较……然后呢? 让我们制作一个表格,跟踪代码并记录它的行为方式与您希望代码的行为方式。

| Order | Your code   | Expected | Result  |
|-------|-------------|----------|---------|
| A<B<C | head = A    | head = A | Correct |
| A<C<B | head = A    | head = A | Correct |
| B<A<C | head = B    | head = B | Correct |
| B<C<A | head = B    | head = B | Correct |
| C<A<B | head = A    | head = C | WRONG!  |
| C<B<A | head = null | head = C | WRONG!  | * note: no recursion in this case

当 C 是最小元素时,看看您的代码如何失败? 你也需要解决这个问题。


最后,有一种解决方案甚至不需要创建 3 路合并。 从技术上讲, merge(A, merge(B, C))应该仍然以 O(n) 时间复杂度运行,并且行为也正确。

祝你总决赛好运!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM