Merging 3 linked lists into 1 (Java )

Viewed 1231

I have a question for a final review for a coding class I am taking. It's asking to merge 3 linked lists into 1 linked list. The problem I am having is when merging the lists I am able to merge the three lists in ascending order but I am missing the last 2 nodes of the 2nd list 23 and 25. I cant figure out why it stops there. The question is here:

Write a program named LinkedTest that:

  1. Creates three sorted singly linked lists of integers as shown below
First List:  2 11 19 21 24

Second List: 14 15 18 23 25

Third List:  3 9 17 20 22
  1. Merges three linked lists into a new sorted linked list as show in the following: 2 3 9 11 14 15 17 18 19 20 21 22 23 24 25
  2. Returns the new sorted linked list Requirement: your program must have a time complexity less than or equal to O(nlog n)

Here is my code:

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; 
    }

    }
}

Output:

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
4 Answers

Why limit yourself to 3-way merges? Let us look at the general case of N-way merging, and then apply that to 3-way (or plain old boring 2-way).

// 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;
    }
}

You can look at this like a lot of chains with links that have numbers on them, where the numbers of each chain are in ascending order. To build a big chain with all numbers in order, you:

  • grab all the small ends of the remaining chains
  • choose the smallest link (if none remains, you have finished)
  • take that link off its chain, and add it to the end of your new chain
  • go back to choosing the smallest link of the old chains

In your answer, as Jacob_G has noted, you were missing some logic for choosing the correct smallest element when one or more lists were empty. Jacob's answer is fine, but I though I'd show you the bigger picture: if you understand it for N, 3 should be no stretch (plus, you gain insight on the general idea).

You're given three sorted lists and are asked to merge them into a single, sorted list. The algorithm to achieve this is very simple.

I recommend keeping track of three indexes (one for each input list) and initialize them all to 0 (the first element of each list). Because each list contains the same number of elements, you can simply iterate from 0 to 3n, where n is the size of one of the lists.

Inside the loop, you want to find the minimum element between the 3 head elements of each list by using the respective indexes. To do this, simply look at the elements and compare them against one-another. Once you have found the minimum, you can append that element to your merged list and increment the respective list's index (this is important as to not append the same element twice).

You'll repeat this 3n times (once for each element in the three lists), and the result will be a single merged list that is sorted in ascending order.

Assuming each input list will always be equal in size, this algorithm is O(3n) which is simplified to O(n).

A pseudo-code solution might look something like this:

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]++

Your problem here is you didn't write all the conditions. Let's look at your use case.

First List: 2 11 19 21 24

Second List: 14 15 18 23 25

Third List: 3 9 17 20 22

According to your code logic, in 1st merge, we get a 2 in first.data < second.data. Next turn we have 11 < 14 in the same location. But after that we now have
19 21 24
14 15 18 23 25
3 9 17 20 22
and none of the conditions will be satisfied in next turns.

I imagine that your code was modified from the code for merging two lists. You should recall why each piece of the code works, and think why your code isn't working the way you think it should work.

Let's assume that your original algorithm looks like this:

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;
}

The first half of the method checks for the need to terminate the recursion. Here, the code terminates when one of the two lists has reached the end. For example, if you are merging the list [1, 3, 5, 7] with the list [2, 4, 6, 8, 9, 10], you are going to end up with merging the list [] with the list [8, 9, 10]. That's when you will need to terminate by returning the list and therefore completing the recursion.

The modification you have made to the algorithm is incorrect because in a three-way merge, you cannot simply return the second list when the first list is out of nodes. The termination criteria has become more complicated and you should pay attention to when you really need to end your recursion. For example, if you have are try to merge [1, 2], [3, 4], [5, 6], assuming the other part of the algorithm works, then you will end up with a merge between [], [3, 4], [5, 6] for which you are going to return [3, 4] and discard [5, 6], which is not correct.

One solution is to call the two-list version of the algorithm when any one list is out. This requires more code (that is quite repetitive too), but is more straightforward coming from this particular code. Another solution is to terminate only when two lists are empty, but this requires special care in the recursive case to check for null values.


The second half of the method compares between the first elements of the lists and uses the smaller element as the head of the new list, then assigns the next data member of the head node to "whatever the result of merging the rest of the lists is". Note that in this version of the code, exactly one of the code blocks is executed every time. (Unless the data are equal, then there would be a bug. Fixing it is left as an exercise.) As long as the recursion is not terminated, we need to go deeper, right!

The modification that you made does not work because you are comparing the first data to the second data, then the second to the third.. then what? Let's make a table, trace the code and record how it behaves vs how you would want the code to behave.

| 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

See how your code fails whenever C is the smallest element? You need to fix this too.


Finally, there's one solution that doesn't even require creating a 3-way merge. Technically, merge(A, merge(B, C)) should still run at O(n) time complexity behaves correctly too.

Good luck on your finals!

Related