package com.freetymekiyan.algorithms.level.hard; import com.freetymekiyan.algorithms.utils.Utils.ListNode; import java.util.PriorityQueue; import java.util.Queue; /** * Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity. * <p> * Company Tags: LinkedIn, Google, Uber, Airbnb, Facebook, Twitter, Amazon, Microsoft * Tags: Divide and Conquer, Linked List, Heap * Similar Problems: (E) Merge Two Sorted Lists, (M) Ugly Number II */ public class MergeKSortedList { /** * Heap. O(k) + O(n * log(k)) Time, O(k) Space. * Keep track of all heads in a min heap, so that we know the next value to be inserted in O(log(k)) time. * Create a min heap of ListNode. * Add all non-null heads to the heap. * Create a dummy head and a current pointer c from dummy. * While heap is not empty: * | Set c.next to the node get from heap top. * | Move c to c.next. * | Now c.next is the new head of that list. * | If c.next is not null, add it to heap. * Return dummy.next, which is the merged head. */ public ListNode mergeKLists(ListNode[] lists) { if (lists == null || lists.length == 0) { return null; } Queue<ListNode> pq = new PriorityQueue<>(lists.length, (n1, n2) -> n1.val - n2.val); for (ListNode n : lists) { if (n != null) { pq.add(n); } } ListNode dummy = new ListNode(0); ListNode cur = dummy; while (!pq.isEmpty()) { cur.next = pq.poll(); cur = cur.next; if (cur.next != null) { pq.add(cur.next); } } return dummy.next; } /** * Divide and conquer. O(nlogn) Time. * Merge k sorted lists can be divided, suppose we have k lists, * 1) Merge the first k / 2 lists * 2) Merge k / 2 + 1 to k lists * Then just implement merge two lists. * Base cases: * 1) If start > end, return null; * 2) If start == end, there is only 1 list, return the head of that list; * 3) If start == end - 1, there are 2 lists, return the merged list. */ public ListNode mergeKListsB(ListNode[] lists) { return mergeKListsB(lists, 0, lists.length - 1); } public ListNode mergeKListsB(ListNode[] lists, int s, int e) { // Base cases. if (s > e) { return null; } if (s == e) { // Only 1 list, just return. return lists[s]; } if (s == e - 1) { // 2 lists, merge. return mergeTwoLists(lists[s], lists[s + 1]); } // Merge the two halves: s to s + (e - s) / 2, s + (e - s) / 2 + 1 to e. return mergeTwoLists(mergeKListsB(lists, s, s + (e - s) / 2), mergeKListsB(lists, s + (e - s) / 2 + 1, e)); } /** * Recursive. * Recurrence Relation: * Pick the node with smaller value as current head h. * Then concatenate h with the merged result of h.next and the other node. * Base case: * 1. l1 is null, l2 is null, return null * 2. l1 is null, l2 is not, return l2. * 3. l1 is not, l2 is null, return l1. * Combined: l1 is null, return l2. l2 is null, return l1. */ private ListNode mergeTwoLists(ListNode l1, ListNode l2) { if (l1 == null) { return l2; } if (l2 == null) { return l1; } // Node with smaller value is the head. if (l1.val < l2.val) { l1.next = mergeTwoLists(l1.next, l2); // Merge head.next and the other head. return l1; } else { l2.next = mergeTwoLists(l1, l2.next); return l2; } } }