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

Posted

技术标签:

【中文标题】将 3 个链表合并为 1 个(Java)【英文标题】:Merging 3 linked lists into 1 (Java ) 【发布时间】:2019-05-19 11:52:33 【问题描述】:

我有一个关于我正在参加的编码课程的期末复习的问题。它要求将 3 个链表合并为 1 个链表。我遇到的问题是在合并列表时,我可以按升序合并三个列表,但我错过了第二个列表 23 和 25 的最后 2 个节点。我不知道为什么它会停在那里。问题在这里:

编写一个名为 LinkedTest 的程序:

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

Second List: 14 15 18 23 25

Third List:  3 9 17 20 22
    将三个链表合并成一个新的排序链表,如下所示:2 3 9 11 14 15 17 18 19 20 21 22 23 24 25 返回新的排序链表 要求:您的程序的时间复杂度必须小于或等于 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

【问题讨论】:

@ScaryWombat 在一个 Java 文件中包含多个类是学术界相当普遍的做法。因为 OP 的课程似乎专注于数据结构和算法,所以我认为它没有什么大错。 @ShioT 好吧,也许 OP 没有注意到,但他对 Third List 的输入和输出不匹配 - 顺便说一句,全世界的普遍做法 也许我把它声明为静态是错误的?对于之前的作业问题,我有一个程序来合并两个链表。所以我用它作为三链表问题的基础。但我无法让第三个链表通过第二个节点。有什么想法吗? @ScaryWombat 编码风格和约定不是这个问题的主题,因为它是基于意见的。我找不到更好的参考,但 here you go. @DanielRogers 输入 int[] data3 = 3, 9, 17, 20, 22 ; 输出 Third List: 149172022 忘记合并,直到你把这部分做对了。 【参考方案1】:

为什么要限制自己进行 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 所指出的,在您的回答中,当一个或多个列表为空时,您缺少一些选择正确最小元素的逻辑。雅各布的回答很好,但我想向您展示更大的图景:如果您对 N 理解它,3 应该毫不费力(另外,您可以深入了解总体思路)。

【讨论】:

这没有为我返回关于合并列表的任何内容? 它适用于我,使用它作为第一条评论建议【参考方案2】:

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

第一个列表: 2 11 19 21 24

第二个列表: 14 15 18 23 25

第三个列表: 3 9 17 20 22

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

【讨论】:

同意,第二个位置的 11 实际上应该是数字 3。所以我 100% 肯定错误在我的 if else 语句的驱动程序中 还要注意你的逻辑处理空链表是错误的。如果first 为空但secondthird 都非空怎么办? 我对原帖做了一些改动。现在我得到合并列表以按升序合并 13 个节点。唯一的问题是最后两个节点 23 和 25 没有合并。我猜一旦列表 3 中的最后一个节点合并,该过程就会停止。 很明显,if (first == null &amp;&amp; second != null) return second; 你的第三个链表呢?其他两个空分支的逻辑相同。您可能需要mergeTwo(ListNode a, ListNode b) 来处理这种情况。【参考方案3】:

给你三个排序的列表,并被要求将它们合并成一个排序的列表。实现这一点的算法非常简单。

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

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

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

假设每个输入 list 的大小总是相等的,这个算法是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]++

【讨论】:

我正在尝试使用这种方法以及其他一些建议的方法。它不会让我发布我让你的伪代码工作的尝试。 您无法编辑您的问题以发布它?也许您可以将您的代码上传到类似的网站:gist.github.com 好的,我编辑了原帖。我对代码做了一些更改。我现在让合并列表返回 2,3,9,11,14,15,17,18,19,20,21,22,24。它缺少第二个链表的最后 2 个节点。 23 和 25。我正在挠头试图弄清楚那部分。 @DanielRogers 看起来你根本没有尝试实现伪代码 我尝试过但失败了。因此,我尝试将您的伪代码调整为我现有的代码。在大多数情况下,它的 90% 都在工作。我只需要弄清楚最后 2 个节点以及为什么在它们出现之前就结束了。【参考方案4】:

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

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

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]、@987654331 @ 你要返回 [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) 时间复杂度运行,也能正常运行。

祝你在决赛中好运!

【讨论】:

好的,我在午休时间编辑了原始帖子。我对代码做了一些更改。我现在让合并列表返回 2,3,9,11,14,15,17,18,19,20,21,22,24。它缺少第二个链表的最后 2 个节点。 23 和 25。我正在挠头试图弄清楚那部分。 检查您的终止条件。当仅用完第三个列表时,您可能正在终止递归。

以上是关于将 3 个链表合并为 1 个(Java)的主要内容,如果未能解决你的问题,请参考以下文章

合并k个有序链表

经典算法——合并K个有序链表

K个排序链表的合并(Hard)

p122 合并 K 个有序链表(leetcode 23)

leetcode(14)-合并k个排序链表

PHP 合并2个链表