Heap · 6 of 18
Merge K sorted arrays
Several sorted lines of numbers become one sorted line by always taking the smallest front number, found fast with a min heap.
- Simple way
- time O(N log N)
room O(N) - Clever way
- time O(N log k)
room O(k)
The problem, simply
You get k arrays. Each one is sorted from small to big. Make one array that holds every number from all of them, sorted from small to big. Let N be the total count of numbers.
For [[1, 5, 9], [2, 3], [4, 8, 10]] the answer is [1, 2, 3, 4, 5, 8, 9, 10].
A story to remember it
Picture it
Three sorted arrays. The min heap holds one front number from each array, tagged with where it came from. The smallest, 1 from A, is on top.
- looking now
- checking
Examples
A normal case
- In
[[1, 5, 9], [2, 3], [4, 8, 10]]- Out
[1, 2, 3, 4, 5, 8, 9, 10]
The arrays have different lengths. When B runs out, the merge keeps going with A and C.
Taking turns
- In
[[1, 4, 7], [2, 5, 8], [3, 6, 9]]- Out
[1, 2, 3, 4, 5, 6, 7, 8, 9]
The answer takes one number from each array in turn.
Repeats and empty arrays
- In
[[2, 2], [], [1, 2]]- Out
[1, 2, 2, 2]
An empty array adds nothing to the heap. Equal numbers all make it into the answer.
One array
- In
[[3, 6, 9]]- Out
[3, 6, 9]
With k = 1 the heap holds one number at a time and the answer is the array itself.
Way 1: the simple way
Throw every number into one big array and sort it. This ignores that the arrays were sorted.
- Copy all
Nnumbers into one array. - Sort it.
import java.util.Arrays;
class Solution {
public int[] mergeKArrays(int[][] arrays) {
int total = 0;
for (int[] a : arrays) total += a.length;
int[] out = new int[total];
int pos = 0;
for (int[] a : arrays) {
for (int x : a) out[pos++] = x;
}
Arrays.sort(out);
return out;
}
}How long it takes: sorting N numbers is O(N log N). Extra room: the output, O(N), which you need anyway.
Way 2: the clever way
Do what the lunch lady does. The heap holds at most one number per array, along with which array it came from and its position there.
- Put the first number of every non-empty array into a min heap as
{value, array, position}. - While the heap is not empty:
- Poll the smallest entry and write its value to the output.
- If its array has a next number, offer that next number.
- Return the output.
Merge [1, 5, 9], [2, 3] and [4, 8, 10]
01 / 11
Put the first number of each array into the min heap: 1 from A, 2 from B, 4 from C. The smallest, 1, is on top.
- looking now
- checking
- done
- just added
import java.util.PriorityQueue;
class Solution {
public int[] mergeKArrays(int[][] arrays) {
int total = 0;
for (int[] a : arrays) total += a.length;
int[] out = new int[total];
// each entry: {value, which array, position in that array}
PriorityQueue<int[]> heap = new PriorityQueue<>((x, y) -> Integer.compare(x[0], y[0]));
for (int i = 0; i < arrays.length; i++) {
if (arrays[i].length > 0) heap.offer(new int[] { arrays[i][0], i, 0 });
}
int pos = 0;
while (!heap.isEmpty()) {
int[] top = heap.poll(); // smallest front number
out[pos++] = top[0];
int a = top[1], next = top[2] + 1;
if (next < arrays[a].length) {
heap.offer(new int[] { arrays[a][next], a, next }); // its array sends the next one
}
}
return out;
}
}How long it takes: each of the N numbers is offered once and polled once on a heap of at most k entries, so O(N log k). Extra room: the heap, O(k), plus the output.
Another way: merge in pairs
Merge array 1 with array 2, array 3 with array 4, and so on, like the rounds of a tournament. Then merge the winners in pairs again. There are log k rounds and each round touches all N numbers, so this is also O(N log k), with no heap at all. Merging one array after another into a growing result is the slow version: it is O(N k), because the early numbers get copied again in every merge.
class Solution {
public int[] mergeKArrays(int[][] arrays) {
if (arrays.length == 0) return new int[0];
return mergeRange(arrays, 0, arrays.length - 1);
}
private int[] mergeRange(int[][] arrays, int lo, int hi) {
if (lo == hi) return arrays[lo];
int mid = (lo + hi) / 2;
return mergeTwo(mergeRange(arrays, lo, mid), mergeRange(arrays, mid + 1, hi));
}
private int[] mergeTwo(int[] a, int[] b) {
int[] out = new int[a.length + b.length];
int i = 0, j = 0, p = 0;
while (i < a.length && j < b.length) out[p++] = a[i] <= b[j] ? a[i++] : b[j++];
while (i < a.length) out[p++] = a[i++];
while (j < b.length) out[p++] = b[j++];
return out;
}
}Watch out for
- Skip empty arrays when you fill the heap at the start, or
arrays[i][0]throws. - Compare with
Integer.compare(x[0], y[0]). Writingx[0] - y[0]overflows when one number is very big and the other very negative. - The heap must never hold two numbers from the same array. If it did, it would grow past
kand lose thelog kspeed. Nandkare different. Say which is which when you give the complexity.
What interviewers ask next
- Merge
ksorted linked lists? The same heap, but each entry is a list node, and the next one isnode.next. - Data too big for memory (external sort)? Sort chunks that fit, write each to disk, then merge all chunks with this heap, reading each file in order. This is how databases sort huge tables.
- Smallest range that covers at least one number from each of the
klists? The same heap of front numbers, plus the current maximum. - K-th smallest number in a matrix whose rows are sorted? Run this merge and stop after
kpolls,O(k log rows). - Heap or pairwise merging? Both are
O(N log k). The heap works on streams that arrive over time. Pairwise merging has no heap overhead and is easy to run in parallel.
Remember this
Grown-up words: the lunch lines are sorted arrays, the stage is a min heap (priority queue) of size k, calling the shortest kid is poll, the next kid stepping up is offer, and the whole thing is a k-way merge.