Hardheap-top-k
Merge k Sorted Lists
Problem (restated)
Merge k sorted linked lists into one sorted list.
Intuition
Always take the smallest current head among k lists via a min-heap.
Approaches
Min-heap of heads
Tested onlyTime O(N log k)Space O(k)
Idea. Heap of (val, list index / node); pop min, push its next.
Walkthrough. [[1,4,5],[1,3,4],[2,6]] → 1→1→2→3→4→4→5→6.
Trade-offs. Heap vs divide-and-conquer merge.
Solution
export class ListNode {
val: number;
next: ListNode | null;
constructor(val = 0, next: ListNode | null = null) {
this.val = val;
this.next = next;
}
}
export function mergeKLists(lists: Array<ListNode | null>): ListNode | null {
const heap: ListNode[] = [];
const push = (n: ListNode) => {
heap.push(n);
heap.sort((a, b) => a.val - b.val);
};
for (const h of lists) if (h) push(h);
const dummy = new ListNode(0);
let cur = dummy;
while (heap.length) {
const n = heap.shift()!;
cur.next = n;
cur = n;
if (n.next) push(n.next);
}
return dummy.next;
}
export class ListNode {
val: number;
next: ListNode | null;
constructor(val = 0, next: ListNode | null = null) {
this.val = val;
this.next = next;
}
}
export function mergeKLists(lists: Array<ListNode | null>): ListNode | null {
const heap: ListNode[] = [];
const push = (n: ListNode) => {
heap.push(n);
heap.sort((a, b) => a.val - b.val);
};
for (const h of lists) if (h) push(h);
const dummy = new ListNode(0);
let cur = dummy;
while (heap.length) {
const n = heap.shift()!;
cur.next = n;
cur = n;
if (n.next) push(n.next);
}
return dummy.next;
}
Template connection
K-way merge with heap.
Pairwise merge
Tested onlyTime O(N log k) typical with divide-conquer; O(Nk) if left-foldSpace O(1) extra
Idea. Merge two sorted lists repeatedly. Heap version is cleaner for k-way; pairwise is fine for teaching merge.
Trade-offs. Heap is preferred in interviews for k-way merge clarity.
Solution
export class ListNode {
val: number;
next: ListNode | null;
constructor(val = 0, next: ListNode | null = null) {
this.val = val;
this.next = next;
}
}
function mergeTwo(a: ListNode | null, b: ListNode | null): ListNode | null {
const dummy = new ListNode(0);
let cur = dummy;
while (a && b) {
if (a.val <= b.val) {
cur.next = a;
a = a.next;
} else {
cur.next = b;
b = b.next;
}
cur = cur.next;
}
cur.next = a ?? b;
return dummy.next;
}
/** Merge lists left-to-right with two-list merge. */
export function mergeKListsPairwise(lists: Array<ListNode | null>): ListNode | null {
let acc: ListNode | null = null;
for (const h of lists) acc = mergeTwo(acc, h);
return acc;
}
export class ListNode {
val: number;
next: ListNode | null;
constructor(val = 0, next: ListNode | null = null) {
this.val = val;
this.next = next;
}
}
function mergeTwo(a: ListNode | null, b: ListNode | null): ListNode | null {
const dummy = new ListNode(0);
let cur = dummy;
while (a && b) {
if (a.val <= b.val) {
cur.next = a;
a = a.next;
} else {
cur.next = b;
b = b.next;
}
cur = cur.next;
}
cur.next = a ?? b;
return dummy.next;
}
/** Merge lists left-to-right with two-list merge. */
export function mergeKListsPairwise(lists: Array<ListNode | null>): ListNode | null {
let acc: ListNode | null = null;
for (const h of lists) acc = mergeTwo(acc, h);
return acc;
}
Reflection
- Which pattern gave this away within 90 seconds?
- What changed from the standard template?
- What would break the current solution?