23. Merge K Soerted List

Borris發表於2019-10-31

Merge Two Sorted List

解決 K sorted list, 我們先解決這個問題。

Solution 1

We can think of a head node and a pointer prev starting from the head.
When compraing l1 and l2 nodes and while both of them or not null, if l1.val is smaller, we can set prev.next to l1, and move l1 to l1.next; else we move l2. And then we move the pointer to prev.next, the node we merged just now.
And if any list reaches null, we merge all the rest of the other list since it is sorted. And we return the head.next becuase the merged list starting from head.next.

class ListNode {
    int val;
    ListNode next;
    public ListNode(int val) {
        this.val = val;
    }
}
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode head = new ListNode(-1);
        ListNode prev = head;
        while (l1 != null && l2 != null) {
            if (l1.val < l2.val) {
                prev.next = l1;
                l1 = l1.next;
            } else {
                prev.next = l2;
                l2 = l2.next;
            }
            prev = prev.next;
        }
        prev.next = l1 == null ? l2 : l1;

        return head.next;
    }
}

Solution 2: Recursion

We may also think in this way. If l1.val is smaller, we will merger l1.next and l2 in the next step and return l1 as the head for next merge; vice versa. And if any of the list is null we just return the other list.

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null) {
            return l2;
        } else if (l2 == null) {
            return l1;
        } else if (l1.val < l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        } else {
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }
}
  • Time complexity for both methods are O(n + m) because they go through each of the node once in both lists.
  • Space complexity for the first one is O(1) and O(n + m) for second one beacuse the recursion won't return until it reaches end of both lists.

Merge K Sorted Lists

解法一

思路

The intuitive way comes to my mind is that I can use the method of merge two sorted linked list. We can use the mergeTwoLists method, to merge k lists one by one k - 1 times.

程式碼
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        int len = lists.length;
        if (len == 0) {
            return null;
        } else if (len == 1) {
            return lists[0];
        }

        ListNode l1 = lists[0];
        for (int  i = 1; i < len; i++) {
            l1 = mergeTwoLists(l1, lists[i]);
        }

        return l1;
    }

    private ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode head = new ListNode(-1);
        ListNode prev = head;
        while (l1 != null && l2 != null) {
            if (l1.val < l2.val) {
                prev.next = l1;
                l1 = l1.next;
            } else {
                prev.next = l2;
                l2 = l2.next;
            }
            prev = prev.next;
        }
        prev.next = l1 == null ? l2 : l1;

        return head.next;
    }   
}
複雜度分析

時間複雜度
Suppose there are k lists and total nodes in all lists are N. Suppose length of each list is equal, then the first merge costs O(N/k + N/k), and second O(2N/k + N/k), ... all the way to O((k - 1)N/k + N/k). Add add k - 1 merges up, it gives O(kN).
空間複雜度
O(1) because merge two linked lists is O(1).

  • 然而使用 recursion 方法時會報錯,不知為何。

解法二

思路

We can use a priority queue. We can first add heads of all lists into the pq. And the node with smaller value always comes before the larger one.
We initialize a head and a pointer to consttuct the merged list. Pay attention to null list node. If there is null node, we do not add it in.
While the queue is not empty, poll the first element from the pq, which is always the smallest one. And we add the next of the polled element to the pq, unless it is null.
Finally, we return the head.next, which is the start of the merged list.

程式碼
class ListNode() {
    int val;
    ListNode next;
    public ListNode(int x) {
        val = x;
    }
}
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if (list.length == 0) {
            return null;
        }
        if (lists.length == 1) {
            return lists[0];
        }
        PriorityQueue<ListNode> pq = new PriorityQueue<>((l1, l2)-> l1.val - l2.val);
        for (ListNode i : lists) {
            if (i != null) { // IMPORTANT!
                pq.offer(i);
            }
        }

        ListNode head = new ListNode(-1);
        ListNode prev = head;
        while (!pq.isEmpty()) {
            ListNode node = pq.poll();
            prev.next = node;
            prev = prev.next;
            if (node.next != null) {
                pq.offer(node.next);
            }
        }

        return head.next;
    }
}
複雜度分析

時間複雜度
O(Nlog(k)) beacuse we process each node exactly once. And the poll() and offer() opertion for each node costs O(log(k)) time.
空間複雜度
We keep the size of the pq O(k). Because there are k nodes initially, and every time we poll a node, we add its next node into the queue.

Takeaway

  • 使用 PriorityQueue 時,一定要確定所用泛型是否可比較。如果不能比較,需要重寫 Comparator。
  • 每次新增節點進 pq, 需要判斷其是否為空。空節點沒有意義。
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章