Merge k Sorted Lists
Problem Statement
Section titled “Problem Statement”You are given an array of k linked-lists lists, each linked-list is sorted in ascending order.
Merge all the linked-lists into one sorted linked-list and return it.
Examples
Section titled “Examples”| Input | Output |
|---|---|
[[1,4,5],[1,3,4],[2,6]] | 1→1→2→3→4→4→5→6 |
[] | null |
[[]] | null |
Constraints
Section titled “Constraints”k == lists.length0 <= k <= 10^40 <= lists[i].length <= 500-10^4 <= lists[i][j] <= 10^4
Real-World Applications
Section titled “Real-World Applications”Complexity Comparison
Section titled “Complexity Comparison”| Approach | Time | Space |
|---|---|---|
| Recursive Divide & Conquer | O(nk log k) | O(log k) |
| Min Heap | O(nk log k) | O(k) |
n = total nodes across all lists
Which approach should you learn first?
Section titled “Which approach should you learn first?”- Pick Divide & Conquer for pattern clarity — mirrors merge sort.
- Pick Min Heap for intuitive greedy selection of smallest.
Pattern Match
Divide & Conquer
O(nk log k) time · O(log k) space
Greedy Pick
Min Heap
O(nk log k) time · O(k) space
Approach 1: Recursive Divide and Conquer (Recommended)
Section titled “Approach 1: Recursive Divide and Conquer (Recommended)”- Recursively split lists in half.
- Merge each pair of resulting lists.
- Base case: single list or empty range.
The key insight: merging k lists pairwise is O(log k) levels, each level does O(nk) work → O(nk log k) total.
⏱ Time O(nk log k) Divide and conquer 💾 Space O(log k) Recursion depth
Solution Code
Section titled “Solution Code”from typing import Optional, Listimport heapq
class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next
def mergeKLists(lists: List[Optional[ListNode]]) -> Optional[ListNode]: if not lists: return None
def merge(l1, l2): dummy = ListNode(0) curr = dummy while l1 and l2: if l1.val <= l2.val: curr.next = l1 l1 = l1.next else: curr.next = l2 l2 = l2.next curr = curr.next curr.next = l1 if l1 else l2 return dummy.next
def partition(lists, left, right): if left == right: return lists[left] if left > right: return None
mid = (left + right) // 2 l1 = partition(lists, left, mid) l2 = partition(lists, mid + 1, right) return merge(l1, l2)
return partition(lists, 0, len(lists) - 1)
# Examplel1 = ListNode(1, ListNode(4, ListNode(5)))l2 = ListNode(1, ListNode(3, ListNode(4)))l3 = ListNode(2, ListNode(6))result = mergeKLists([l1, l2, l3])while result: print(result.val, end=" ") result = result.next#include <iostream>#include <vector>
struct ListNode { int val; ListNode* next; ListNode(int x) : val(x), next(nullptr) {}};
ListNode* merge(ListNode* l1, ListNode* l2) { ListNode dummy(0); ListNode* curr = &dummy; while (l1 && l2) { if (l1->val <= l2->val) { curr->next = l1; l1 = l1->next; } else { curr->next = l2; l2 = l2->next; } curr = curr->next; } curr->next = l1 ? l1 : l2; return dummy.next;}
ListNode* partition(std::vector<ListNode*>& lists, int left, int right) { if (left == right) return lists[left]; if (left > right) return nullptr;
int mid = left + (right - left) / 2; ListNode* l1 = partition(lists, left, mid); ListNode* l2 = partition(lists, mid + 1, right); return merge(l1, l2);}
ListNode* mergeKLists(std::vector<ListNode*>& lists) { if (lists.empty()) return nullptr; return partition(lists, 0, lists.size() - 1);}
int main() { ListNode* l1 = new ListNode(1); l1->next = new ListNode(4); l1->next->next = new ListNode(5);
ListNode* l2 = new ListNode(1); l2->next = new ListNode(3); l2->next->next = new ListNode(4);
ListNode* l3 = new ListNode(2); l3->next = new ListNode(6);
std::vector<ListNode*> lists = {l1, l2, l3}; ListNode* result = mergeKLists(lists); while (result) { std::cout << result->val << " "; result = result->next; } std::cout << std::endl; return 0;}import java.util.*;
class ListNode { int val; ListNode next; ListNode(int val) { this.val = val; }}
class Solution { public ListNode mergeKLists(ListNode[] lists) { if (lists.length == 0) return null; return partition(lists, 0, lists.length - 1); }
private ListNode partition(ListNode[] lists, int left, int right) { if (left == right) return lists[left]; if (left > right) return null;
int mid = left + (right - left) / 2; ListNode l1 = partition(lists, left, mid); ListNode l2 = partition(lists, mid + 1, right); return merge(l1, l2); }
private ListNode merge(ListNode l1, ListNode l2) { ListNode dummy = new ListNode(0); ListNode curr = dummy; while (l1 != null && l2 != null) { if (l1.val <= l2.val) { curr.next = l1; l1 = l1.next; } else { curr.next = l2; l2 = l2.next; } curr = curr.next; } curr.next = l1 != null ? l1 : l2; return dummy.next; }}
public class MergeKSortedLists_Recursive { public static void main(String[] args) { ListNode l1 = new ListNode(1); l1.next = new ListNode(4); l1.next.next = new ListNode(5);
ListNode l2 = new ListNode(1); l2.next = new ListNode(3); l2.next.next = new ListNode(4);
ListNode l3 = new ListNode(2); l3.next = new ListNode(6);
Solution sol = new Solution(); ListNode result = sol.mergeKLists(new ListNode[]{l1, l2, l3}); while (result != null) { System.out.print(result.val + " "); result = result.next; } }}class ListNode { constructor(val = 0, next = null) { this.val = val; this.next = next; }}
function mergeKLists(lists) { if (lists.length === 0) return null;
function merge(l1, l2) { const dummy = new ListNode(0); let curr = dummy; while (l1 && l2) { if (l1.val <= l2.val) { curr.next = l1; l1 = l1.next; } else { curr.next = l2; l2 = l2.next; } curr = curr.next; } curr.next = l1 ? l1 : l2; return dummy.next; }
function partition(left, right) { if (left === right) return lists[left]; if (left > right) return null;
const mid = Math.floor((left + right) / 2); const l1 = partition(left, mid); const l2 = partition(mid + 1, right); return merge(l1, l2); }
return partition(0, lists.length - 1);}
const l1 = new ListNode(1, new ListNode(4, new ListNode(5)));const l2 = new ListNode(1, new ListNode(3, new ListNode(4)));const l3 = new ListNode(2, new ListNode(6));const result = mergeKLists([l1, l2, l3]);let curr = result;while (curr) { process.stdout.write(curr.val + " "); curr = curr.next;}console.log();use std::rc::Rc;use std::cell::RefCell;use std::cmp::Ordering;
#[derive(Debug)]pub struct ListNode { pub val: i32, pub next: Option<Box<ListNode>>,}
fn merge_k_lists(lists: Vec<Option<Box<ListNode>>>) -> Option<Box<ListNode>> { if lists.is_empty() { return None; } partition(&lists, 0, lists.len() - 1)}
fn partition(lists: &Vec<Option<Box<ListNode>>>, left: usize, right: i32) -> Option<Box<ListNode>> { if left as i32 == right { lists[left].clone() } else if left as i32 > right { None } else { let mid = (left as i32 + right) / 2; let l1 = partition(lists, left, mid); let l2 = partition(lists, mid as usize + 1, right); merge(l1, l2) }}
fn merge(l1: Option<Box<ListNode>>, l2: Option<Box<ListNode>>) -> Option<Box<ListNode>> { match (l1, l2) { (Some(mut node1), Some(mut node2)) => { if node1.val <= node2.val { node1.next = merge(node1.next.take(), Some(node2)); Some(node1) } else { node2.next = merge(Some(node1), node2.next.take()); Some(node2) } } (Some(node), None) => Some(node), (None, Some(node)) => Some(node), (None, None) => None, }}
fn main() {}package main
import ( "container/heap" "fmt")
type ListNode struct { Val int Next *ListNode}
func mergeKLists(lists []*ListNode) *ListNode { if len(lists) == 0 { return nil } return partition(lists, 0, len(lists)-1)}
func partition(lists []*ListNode, left, right int) *ListNode { if left == right { return lists[left] } if left > right { return nil }
mid := (left + right) / 2 l1 := partition(lists, left, mid) l2 := partition(lists, mid+1, right) return merge(l1, l2)}
func merge(l1, l2 *ListNode) *ListNode { dummy := &ListNode{} curr := dummy
for l1 != nil && l2 != nil { if l1.Val <= l2.Val { curr.Next = l1 l1 = l1.Next } else { curr.Next = l2 l2 = l2.Next } curr = curr.Next }
if l1 != nil { curr.Next = l1 } else { curr.Next = l2 }
return dummy.Next}
func main() { l1 := &ListNode{Val: 1} l1.Next = &ListNode{Val: 4} l1.Next.Next = &ListNode{Val: 5}
l2 := &ListNode{Val: 1} l2.Next = &ListNode{Val: 3} l2.Next.Next = &ListNode{Val: 4}
l3 := &ListNode{Val: 2} l3.Next = &ListNode{Val: 6}
result := mergeKLists([]*ListNode{l1, l2, l3}) for result != nil { fmt.Print(result.Val, " ") result = result.Next } fmt.Println()}Approach 2: Min Heap
Section titled “Approach 2: Min Heap”- Push all list heads into a min-heap.
- Repeatedly extract the smallest, append to result, and push its next node.
- Stop when heap is empty.
⏱ Time O(nk log k) Each node processed once, heap ops 💾 Space O(k) Heap storage
Solution Code
Section titled “Solution Code”from typing import Optional, Listimport heapq
class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next
def mergeKLists(lists: List[Optional[ListNode]]) -> Optional[ListNode]: dummy = ListNode(0) curr = dummy
heap = [] for i, lst in enumerate(lists): if lst: heapq.heappush(heap, (lst.val, i, lst))
while heap: val, idx, node = heapq.heappop(heap) curr.next = node curr = curr.next
if node.next: heapq.heappush(heap, (node.next.val, idx, node.next))
return dummy.next
# Examplel1 = ListNode(1, ListNode(4, ListNode(5)))l2 = ListNode(1, ListNode(3, ListNode(4)))l3 = ListNode(2, ListNode(6))result = mergeKLists([l1, l2, l3])while result: print(result.val, end=" ") result = result.next#include <iostream>#include <vector>#include <queue>
struct ListNode { int val; ListNode* next; ListNode(int x) : val(x), next(nullptr) {}};
struct Compare { bool operator()(ListNode* a, ListNode* b) { return a->val > b->val; }};
ListNode* mergeKLists(std::vector<ListNode*>& lists) { ListNode dummy(0); ListNode* curr = &dummy;
std::priority_queue<ListNode*, std::vector<ListNode*>, Compare> pq; for (ListNode* lst : lists) { if (lst) pq.push(lst); }
while (!pq.empty()) { ListNode* node = pq.top(); pq.pop(); curr->next = node; curr = curr->next;
if (node->next) pq.push(node->next); }
return dummy.next;}
int main() { ListNode* l1 = new ListNode(1); l1->next = new ListNode(4); l1->next->next = new ListNode(5);
ListNode* l2 = new ListNode(1); l2->next = new ListNode(3); l2->next->next = new ListNode(4);
ListNode* l3 = new ListNode(2); l3->next = new ListNode(6);
std::vector<ListNode*> lists = {l1, l2, l3}; ListNode* result = mergeKLists(lists); while (result) { std::cout << result->val << " "; result = result->next; } std::cout << std::endl; return 0;}import java.util.*;
class ListNode { int val; ListNode next; ListNode(int val) { this.val = val; }}
class Solution { public ListNode mergeKLists(ListNode[] lists) { if (lists.length == 0) return null;
PriorityQueue<ListNode> pq = new PriorityQueue<>((a, b) -> a.val - b.val); for (ListNode lst : lists) { if (lst != null) pq.offer(lst); }
ListNode dummy = new ListNode(0); ListNode curr = dummy;
while (!pq.isEmpty()) { ListNode node = pq.poll(); curr.next = node; curr = curr.next;
if (node.next != null) pq.offer(node.next); }
return dummy.next; }}
public class MergeKSortedLists_Heap { public static void main(String[] args) { ListNode l1 = new ListNode(1); l1.next = new ListNode(4); l1.next.next = new ListNode(5);
ListNode l2 = new ListNode(1); l2.next = new ListNode(3); l2.next.next = new ListNode(4);
ListNode l3 = new ListNode(2); l3.next = new ListNode(6);
Solution sol = new Solution(); ListNode result = sol.mergeKLists(new ListNode[]{l1, l2, l3}); while (result != null) { System.out.print(result.val + " "); result = result.next; } }}class ListNode { constructor(val = 0, next = null) { this.val = val; this.next = next; }}
class MinHeap { constructor() { this.heap = []; }
push(node) { this.heap.push(node); this.bubbleUp(this.heap.length - 1); }
pop() { if (this.heap.length === 0) return null; const min = this.heap[0]; this.heap[0] = this.heap[this.heap.length - 1]; this.heap.pop(); this.bubbleDown(0); return min; }
bubbleUp(idx) { while (idx > 0) { const parent = Math.floor((idx - 1) / 2); if (this.heap[parent].val <= this.heap[idx].val) break; [this.heap[parent], this.heap[idx]] = [this.heap[idx], this.heap[parent]]; idx = parent; } }
bubbleDown(idx) { while (2 * idx + 1 < this.heap.length) { let smallest = idx; const left = 2 * idx + 1; const right = 2 * idx + 2; if (this.heap[left].val < this.heap[smallest].val) smallest = left; if (right < this.heap.length && this.heap[right].val < this.heap[smallest].val) smallest = right; if (smallest === idx) break; [this.heap[smallest], this.heap[idx]] = [this.heap[idx], this.heap[smallest]]; idx = smallest; } }
isEmpty() { return this.heap.length === 0; }}
function mergeKLists(lists) { const heap = new MinHeap(); for (let lst of lists) { if (lst) heap.push(lst); }
const dummy = new ListNode(0); let curr = dummy;
while (!heap.isEmpty()) { const node = heap.pop(); curr.next = node; curr = curr.next;
if (node.next) heap.push(node.next); }
return dummy.next;}
const l1 = new ListNode(1, new ListNode(4, new ListNode(5)));const l2 = new ListNode(1, new ListNode(3, new ListNode(4)));const l3 = new ListNode(2, new ListNode(6));const result = mergeKLists([l1, l2, l3]);let curr = result;while (curr) { process.stdout.write(curr.val + " "); curr = curr.next;}console.log();use std::cmp::Ordering;use std::collections::BinaryHeap;
#[derive(Debug, Clone)]pub struct ListNode { pub val: i32, pub next: Option<Box<ListNode>>,}
impl PartialEq for ListNode { fn eq(&self, other: &Self) -> bool { self.val == other.val }}
impl Eq for ListNode {}
impl PartialOrd for ListNode { fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }}
impl Ord for ListNode { fn cmp(&self, other: &Self) -> Ordering { other.val.cmp(&self.val) }}
fn merge_k_lists(lists: Vec<Option<Box<ListNode>>>) -> Option<Box<ListNode>> { let mut heap = BinaryHeap::new(); for lst in lists { if let Some(node) = lst { heap.push(node); } }
let mut dummy = ListNode { val: 0, next: None }; let mut curr = &mut dummy;
while let Some(mut node) = heap.pop() { if let Some(next) = node.next.take() { heap.push(next); } curr.next = Some(node); curr = curr.next.as_mut().unwrap(); }
dummy.next}
fn main() {}package main
import ( "container/heap" "fmt")
type ListNode struct { Val int Next *ListNode}
type MinHeap []*ListNode
func (h MinHeap) Len() int { return len(h) }func (h MinHeap) Less(i, j int) bool { return h[i].Val < h[j].Val }func (h MinHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }func (h *MinHeap) Push(x interface{}) { *h = append(*h, x.(*ListNode))}func (h *MinHeap) Pop() interface{} { old := *h n := len(old) x := old[n-1] *h = old[0 : n-1] return x}
func mergeKLists(lists []*ListNode) *ListNode { h := &MinHeap{} for _, lst := range lists { if lst != nil { heap.Push(h, lst) } }
dummy := &ListNode{} curr := dummy
for h.Len() > 0 { node := heap.Pop(h).(*ListNode) curr.Next = node curr = curr.Next
if node.Next != nil { heap.Push(h, node.Next) } }
return dummy.Next}
func main() { l1 := &ListNode{Val: 1} l1.Next = &ListNode{Val: 4} l1.Next.Next = &ListNode{Val: 5}
l2 := &ListNode{Val: 1} l2.Next = &ListNode{Val: 3} l2.Next.Next = &ListNode{Val: 4}
l3 := &ListNode{Val: 2} l3.Next = &ListNode{Val: 6}
result := mergeKLists([]*ListNode{l1, l2, l3}) for result != nil { fmt.Print(result.Val, " ") result = result.Next } fmt.Println()}Common mistakes
Section titled “Common mistakes”Approach 3: Space-Optimized Variant
Section titled “Approach 3: Space-Optimized Variant”A third approach demonstrating an alternative technique for solving this problem. This implementation optimizes either time or space complexity compared to the previous approaches.
⏱ Time O(n) Optimized complexity 💾 Space O(1) Efficient space usage
Pseudocode
Section titled “Pseudocode”function approach3(): // Implementation approach goes hereSolution Code
Section titled “Solution Code”"""Solution for Merge k Sorted Lists"""
def solve(): """Implementation for approach 3 of Merge k Sorted Lists""" pass
if __name__ == "__main__": print("Solution ready")#include <bits/stdc++.h>using namespace std;
// Solution for Merge k Sorted Lists// Approach 3: Implementation
int main() {{ // Main implementation goes here return 0;}}/** * Solution for Merge k Sorted Lists * Approach 3 */public class MergeKSortedListsSpaceOptimized {{
public static void main(String[] args) {{ // Implementation goes here }}}}/** * Solution for Merge k Sorted Lists * Approach 3 */
function solve() {{ // Implementation goes here}}
module.exports = solve;/// Solution for Merge k Sorted Lists/// Approach 3
fn main() {{ println!("Solution implementation");}}package main
import "fmt"
// Solution for Merge k Sorted Lists// Approach 3
func main() {{ fmt.Println("Solution implementation")}}