Merge K Sorted Lists Using Min Heap in DSA C++ - Time & Space Complexity
We want to know how the time to merge multiple sorted lists grows as the number of lists and their sizes increase.
How does using a min heap help us merge efficiently?
Analyze the time complexity of the following code snippet.
struct ListNode {
int val;
ListNode* next;
};
ListNode* mergeKLists(std::vector& lists) {
auto cmp = [](ListNode* a, ListNode* b){return a->val > b->val;};
std::priority_queue<ListNode*, std::vector<ListNode*>, decltype(cmp)> minHeap(cmp);
for (auto list : lists) {
if (list) minHeap.push(list);
}
ListNode dummy; ListNode* tail = &dummy;
while (!minHeap.empty()) {
auto node = minHeap.top(); minHeap.pop();
tail->next = node; tail = tail->next;
if (node->next) minHeap.push(node->next);
}
return dummy.next;
}
This code merges k sorted linked lists into one sorted list using a min heap to always pick the smallest current node.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Extracting the smallest node from the min heap and inserting the next node from that list.
- How many times: This happens once for each node across all lists, so total n times where n is the total number of nodes.
Each of the n nodes is pushed and popped from the min heap, which has size at most k (number of lists).
| Input Size (n) | Approx. Operations |
|---|---|
| 10 (k=3) | About 10 insertions and removals from a heap of size 3 |
| 100 (k=5) | About 100 insertions and removals from a heap of size 5 |
| 1000 (k=10) | About 1000 insertions and removals from a heap of size 10 |
Pattern observation: The number of operations grows roughly linearly with total nodes n, but each heap operation costs log k time, so total work grows as n times log k.
Time Complexity: O(n log k)
This means the time grows linearly with total nodes n, multiplied by the cost of managing the heap of size k.
[X] Wrong: "Merging k lists is just like merging two lists, so it takes O(n) time."
[OK] Correct: Merging k lists involves repeatedly choosing the smallest among k current nodes, which requires extra work using a heap, making it slower than just merging two lists.
Understanding this time complexity helps you explain why using a min heap is efficient for merging multiple sorted lists, a common problem in coding interviews.
"What if we used a simple linear search to find the smallest node instead of a min heap? How would the time complexity change?"
Practice
Solution
Step 1: Understand the role of min heap in merging
A min heap helps quickly find the smallest element among multiple sorted lists by always keeping the smallest element at the top.Step 2: Compare with other options
Options A, B, and C do not describe the min heap's advantage correctly. The min heap does not sort lists individually or concatenate without sorting.Final Answer:
It efficiently finds the smallest element among the heads of all lists. -> Option AQuick Check:
Min heap = efficient smallest element retrieval [OK]
- Thinking min heap sorts entire lists
- Assuming min heap stores all elements at once
- Confusing min heap with simple concatenation
Solution
Step 1: Identify min heap declaration syntax
In C++, to create a min heap, use std::priority_queue with std::greater comparator and std::vector as container.Step 2: Check each option
std::priority_queue minHeap; minHeap.push(5); uses std::greater and vector correctly. std::priority_queue<int> minHeap; minHeap.push(5); is max heap by default. std::priority_queue minHeap; minHeap.push(5); uses std::less which is max heap. std::priority_queue minHeap; minHeap.push(5); uses std::list which is invalid container for priority_queue.Final Answer:
std::priority_queue minHeap; minHeap.push(5); -> Option CQuick Check:
Min heap = priority_queue + greater + vector [OK]
- Using default priority_queue (max heap)
- Using std::less instead of std::greater
- Using invalid container like std::list
#include <iostream>
#include <vector>
#include <queue>
struct Node {
int val;
int listIndex;
int elementIndex;
Node(int v, int li, int ei) : val(v), listIndex(li), elementIndex(ei) {}
};
struct Compare {
bool operator()(Node const& a, Node const& b) {
return a.val > b.val;
}
};
int main() {
std::vector lists = {{1,4,5}, {1,3,4}, {2,6}};
std::priority_queue minHeap;
for (int i = 0; i < lists.size(); i++) {
if (!lists[i].empty())
minHeap.push(Node(lists[i][0], i, 0));
}
std::vector<int> merged;
while (!minHeap.empty()) {
Node curr = minHeap.top();
minHeap.pop();
merged.push_back(curr.val);
int nextIndex = curr.elementIndex + 1;
if (nextIndex < lists[curr.listIndex].size()) {
minHeap.push(Node(lists[curr.listIndex][nextIndex], curr.listIndex, nextIndex));
}
}
for (int val : merged) std::cout << val << " ";
return 0;
}
Solution
Step 1: Understand the merging process
The code pushes the first element of each list into the min heap. Then it repeatedly extracts the smallest element and pushes the next element from the same list until all elements are merged.Step 2: Trace the merged output
Lists: {1,4,5}, {1,3,4}, {2,6}. Merged order: 1 (from list0), 1 (list1), 2 (list2), 3 (list1), 4 (list0), 4 (list1), 5 (list0), 6 (list2).Final Answer:
1 1 2 3 4 4 5 6 -> Option AQuick Check:
Merge sorted lists = 1 1 2 3 4 4 5 6 [OK]
- Mixing order of elements
- Forgetting to push next element after pop
- Ignoring duplicates in merged output
std::priority_queue minHeap;
for (auto& list : lists) {
for (int val : list) {
minHeap.push(val);
}
}
std::vector<int> merged;
while (!minHeap.empty()) {
merged.push_back(minHeap.top());
minHeap.pop();
}
Solution
Step 1: Analyze the approach used
The code pushes all elements from all lists into the min heap at once, then pops them to get sorted order.Step 2: Identify inefficiency
This approach loses the efficiency of merging K sorted lists using min heap by only pushing the first element of each list and pushing next elements on demand.Final Answer:
The code pushes all elements at once, losing the benefit of merging sorted lists efficiently. -> Option BQuick Check:
Push all elements at once = inefficient merge [OK]
- Pushing all elements at once
- Ignoring heap declaration correctness
- Forgetting to pop elements in loop
Solution
Step 1: Handle empty lists and duplicates
Skip empty lists when initializing the min heap. For duplicates, min heap naturally handles them by ordering values.Step 2: Efficient merging strategy
Push only the first element of each non-empty list into the min heap. When popping, push the next element from the same list if it exists. This keeps memory and operations efficient.Final Answer:
Initialize the min heap with the first non-empty element of each list and push next elements only when popping, allowing duplicates naturally. -> Option DQuick Check:
Push first non-empty elements, push next on pop, duplicates allowed [OK]
- Pushing all elements at once wasting memory
- Ignoring empty lists causing errors
- Using max heap which breaks order
Ready to prove it?
Pass the test and this pattern moves unknown
