https://oj.leetcode.com/problems/merge-k-sorted-lists/
Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.
Solution:
1. O(nk2) runtime, O(1) space – Brute force:
The brute force approach is to merge a list one by one. For example, if the lists = [l1, l2, l3, l4], we first merge l1 and l2, then merge the result with l3, and finally l4.
To analyze its time complexity, we are going to assume there are a total of k lists, and each list is of size n. There will be a total of k–1 merge operations. The first merge operation will be two lists of size n, therefore in the worst case there could be n + n comparisons. The second merge operation will be two lists of size 2n and n. Notice that each merge increase the size of the merged lists by n. Therefore, the total number of comparisons required is 2n + 3n + 4n + … + kn = n(k(k+1)/2 -1) = O(nk2).
2. O(nklogk) runtime, O(k) space – Heap:
We could use a min heap of size k. The heap is first initialized with the smallest element from each list. Then as we extract the nodes out from the heap, we must remember to insert its next node into the heap. As each insert operation into the heap costs log(k) and there are a total of nk elements, the total runtime complexity is O(nklogk).
Ignoring the extra space that is used to store the output list, we only use extra space of O(k) due to the heap.
/** * Author : Acjx * Email : zhoujx0219@163.com */ /** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Cmp { public: bool operator() (const ListNode *lhs, const ListNode *rhs) { return lhs->val > rhs->val; } }; class Solution { public: ListNode *mergeKLists(vector<ListNode *> &lists) { if (lists.empty()) return NULL; priority_queue<ListNode, vector<ListNode *>, Cmp> queue; for (ListNode *&nodePtr : lists) { if (nodePtr != NULL) { queue.push(nodePtr); } } ListNode *dummyHead = new ListNode(0); ListNode *p = dummyHead; while (!queue.empty()) { ListNode *nodePtr = queue.top(); queue.pop(); p->next = nodePtr; p = p->next; if (nodePtr->next != NULL) { queue.push(nodePtr->next); } } return dummyHead->next; } };
3. O(nk log k) runtime, O(1) space – Divide and conquer using two way merge:
If you still remember how merge sort works, we can use a divide and conquer mechanism to solve this problem. Here, we apply the merge two lists algorithm from Article[Merge Two Sorted Lists].
Basically, the algorithm merges two lists at a time, so the number of lists reduces from:
k –> k/2 –> k/4 –> … –> 2 –> 1
Similarly, the size of the lists increases from (Note that the lists could subdivide itself at most log(k) times):
n –> 2n –> 4n –> … –> 2logkn
Therefore, the runtime complexity is:
k * n + k/2 * 2n + k/4 * 4n + … + 2logkn * 1
= nk + nk + nk + … + nk
= nklogk
Since we are implementing this divide and conquer algorithm iteratively, the space complexity is constant at O(1), yay!
/** * Author : Acjx * Email : zhoujx0219@163.com */ /** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: ListNode *mergeKLists(vector<ListNode *> &lists) { if (lists.empty()) return NULL; int end = lists.size() - 1; while (end > 0) { int begin = 0; while (begin < end) { lists[begin] = merge2Lists(lists[begin], lists[end]); ++begin; --end; } } return lists.at(0); } private: ListNode *merge2Lists(ListNode *l1, ListNode *l2) { ListNode *dummyHead = new ListNode(0); ListNode *p = dummyHead; while (l1 != NULL && l2 != NULL) { if (l1->val < l2->val) { p->next = l1; l1 = l1->next; } else { p->next = l2; l2 = l2->next; } p = p->next; } if (l1 != NULL) p->next = l1; if (l2 != NULL) p->next = l2; return dummyHead->next; } };