Merge sort
Merge sort splits the array in half, sorts each half recursively, and merges the two sorted halves. It guarantees O(n log n) time for every input and is stable, at the price of O(n) extra memory.
Learning goals
- Trace the recursion tree and the merge step of top-down merge sort
- Write a stable merge that uses one reusable buffer
- Convert the recursion into the bottom-up version
- Sort a linked list with merge sort in O(1) extra space
Prerequisites
Merging two sorted piles of exam papers is easy: keep comparing the top papers and take the lower one. Merge sort sorts a big pile by sorting two halves first, then doing that cheap merge.
Divide and conquer
Divide: split arr[lo..hi] at mid. Conquer: sort both halves recursively. Combine: merge the two sorted halves. A single element is already sorted, which is the base case.
The recursion tree has about log₂ n levels, and the merges on each level touch every element once. That gives n work per level and O(n log n) in total, regardless of the input order. Step through the animation and watch the call stack, the tmp[] buffer and the sorted runs grow.
Best
O(n log n)
Average
O(n log n)
Worst
O(n log n)
Space
O(n)
Merge sort (top-down)
Interactive · edit the inputint[] arr · length 8
Divide the 8 elements until single elements remain, then merge sorted runs back together.
1public static void mergeSort(int[] arr, int lo, int hi) {2if (hi - lo < 1) return;3int mid = lo + (hi - lo) / 2;4mergeSort(arr, lo, mid);5mergeSort(arr, mid + 1, hi);6merge(arr, lo, mid, hi);7}89static void merge(int[] arr, int lo, int mid, int hi) {10int[] tmp = new int[hi - lo + 1];11int i = lo, j = mid + 1, k = 0;12while (i <= mid && j <= hi) {13if (arr[i] <= arr[j]) tmp[k++] = arr[i++];14else tmp[k++] = arr[j++];15}16while (i <= mid) tmp[k++] = arr[i++];17while (j <= hi) tmp[k++] = arr[j++];18System.arraycopy(tmp, 0, arr, lo, tmp.length);19}
Live state
n
8
Execution trace (dry run table)Show 94 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | n=8 | Divide the 8 elements until single elements remain, then merge sorted runs back together. |
| 2 | L1 | lo=0 hi=7 size=8 | Enter mergeSort(arr, 0, 7) on 8 elements. |
| 3 | L2 | lo=0 hi=7 | More than one element, so split in half. |
| 4 | L3 | lo=0 mid=3 hi=7 | mid = 3. Left half [0..3], right half [4..7]. |
| 5 | L4 | lo=0 hi=3 size=4 | Enter mergeSort(arr, 0, 3) on 4 elements. |
| 6 | L2 | lo=0 hi=3 | More than one element, so split in half. |
| 7 | L3 | lo=0 mid=1 hi=3 | mid = 1. Left half [0..1], right half [2..3]. |
| 8 | L4 | lo=0 hi=1 size=2 | Enter mergeSort(arr, 0, 1) on 2 elements. |
| 9 | L2 | lo=0 hi=1 | More than one element, so split in half. |
| 10 | L3 | lo=0 mid=0 hi=1 | mid = 0. Left half [0..0], right half [1..1]. |
| 11 | L4 | lo=0 hi=0 size=1 | Enter mergeSort(arr, 0, 0) on 1 element. |
| 12 | L2 | lo=0 hi=0 | Base case: one element is already sorted. |
| 13 | L5 | lo=1 hi=1 size=1 | Enter mergeSort(arr, 1, 1) on 1 element. |
| 14 | L2 | lo=1 hi=1 | Base case: one element is already sorted. |
| 15 | L10 | lo=0 mid=0 hi=1 i=0 j=1 k=0 | Allocate tmp for 2 elements. Left run [0..0] and right run [1..1] are each already sorted. |
| 16 | L13 | lo=0 mid=0 hi=1 i=0 j=1 k=0 | The right head is strictly smaller, so take it. |
| 17 | L14 | lo=0 mid=0 hi=1 i=0 j=2 k=1 | tmp[0] = 27. |
| 18 | L16 | lo=0 mid=0 hi=1 i=1 j=2 k=2 | The left run still has 1 element. They are already sorted and no larger run remains, so copy them all. |
| 19 | L18 | lo=0 mid=0 hi=1 i=1 j=2 k=2 | Copy tmp back into arr[0..1]. That range is now sorted. |
| 20 | L5 | lo=2 hi=3 size=2 | Enter mergeSort(arr, 2, 3) on 2 elements. |
| 21 | L2 | lo=2 hi=3 | More than one element, so split in half. |
| 22 | L3 | lo=2 mid=2 hi=3 | mid = 2. Left half [2..2], right half [3..3]. |
| 23 | L4 | lo=2 hi=2 size=1 | Enter mergeSort(arr, 2, 2) on 1 element. |
| 24 | L2 | lo=2 hi=2 | Base case: one element is already sorted. |
| 25 | L5 | lo=3 hi=3 size=1 | Enter mergeSort(arr, 3, 3) on 1 element. |
| 26 | L2 | lo=3 hi=3 | Base case: one element is already sorted. |
| 27 | L10 | lo=2 mid=2 hi=3 i=2 j=3 k=0 | Allocate tmp for 2 elements. Left run [2..2] and right run [3..3] are each already sorted. |
| 28 | L13 | lo=2 mid=2 hi=3 i=2 j=3 k=0 | The right head is strictly smaller, so take it. |
| 29 | L14 | lo=2 mid=2 hi=3 i=2 j=4 k=1 | tmp[0] = 3. |
| 30 | L16 | lo=2 mid=2 hi=3 i=3 j=4 k=2 | The left run still has 1 element. They are already sorted and no larger run remains, so copy them all. |
| 31 | L18 | lo=2 mid=2 hi=3 i=3 j=4 k=2 | Copy tmp back into arr[2..3]. That range is now sorted. |
| 32 | L10 | lo=0 mid=1 hi=3 i=0 j=2 k=0 | Allocate tmp for 4 elements. Left run [0..1] and right run [2..3] are each already sorted. |
| 33 | L13 | lo=0 mid=1 hi=3 i=0 j=2 k=0 | The right head is strictly smaller, so take it. |
| 34 | L14 | lo=0 mid=1 hi=3 i=0 j=3 k=1 | tmp[0] = 3. |
| 35 | L13 | lo=0 mid=1 hi=3 i=0 j=3 k=1 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 36 | L13 | lo=0 mid=1 hi=3 i=1 j=3 k=2 | tmp[1] = 27. |
| 37 | L13 | lo=0 mid=1 hi=3 i=1 j=3 k=2 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 38 | L13 | lo=0 mid=1 hi=3 i=2 j=3 k=3 | tmp[2] = 38. |
| 39 | L17 | lo=0 mid=1 hi=3 i=2 j=4 k=4 | The right run still has 1 element. They are already sorted and no larger run remains, so copy them all. |
| 40 | L18 | lo=0 mid=1 hi=3 i=2 j=4 k=4 | Copy tmp back into arr[0..3]. That range is now sorted. |
| 41 | L5 | lo=4 hi=7 size=4 | Enter mergeSort(arr, 4, 7) on 4 elements. |
| 42 | L2 | lo=4 hi=7 | More than one element, so split in half. |
| 43 | L3 | lo=4 mid=5 hi=7 | mid = 5. Left half [4..5], right half [6..7]. |
| 44 | L4 | lo=4 hi=5 size=2 | Enter mergeSort(arr, 4, 5) on 2 elements. |
| 45 | L2 | lo=4 hi=5 | More than one element, so split in half. |
| 46 | L3 | lo=4 mid=4 hi=5 | mid = 4. Left half [4..4], right half [5..5]. |
| 47 | L4 | lo=4 hi=4 size=1 | Enter mergeSort(arr, 4, 4) on 1 element. |
| 48 | L2 | lo=4 hi=4 | Base case: one element is already sorted. |
| 49 | L5 | lo=5 hi=5 size=1 | Enter mergeSort(arr, 5, 5) on 1 element. |
| 50 | L2 | lo=5 hi=5 | Base case: one element is already sorted. |
| 51 | L10 | lo=4 mid=4 hi=5 i=4 j=5 k=0 | Allocate tmp for 2 elements. Left run [4..4] and right run [5..5] are each already sorted. |
| 52 | L13 | lo=4 mid=4 hi=5 i=4 j=5 k=0 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 53 | L13 | lo=4 mid=4 hi=5 i=5 j=5 k=1 | tmp[0] = 9. |
| 54 | L17 | lo=4 mid=4 hi=5 i=5 j=6 k=2 | The right run still has 1 element. They are already sorted and no larger run remains, so copy them all. |
| 55 | L18 | lo=4 mid=4 hi=5 i=5 j=6 k=2 | Copy tmp back into arr[4..5]. That range is now sorted. |
| 56 | L5 | lo=6 hi=7 size=2 | Enter mergeSort(arr, 6, 7) on 2 elements. |
| 57 | L2 | lo=6 hi=7 | More than one element, so split in half. |
| 58 | L3 | lo=6 mid=6 hi=7 | mid = 6. Left half [6..6], right half [7..7]. |
| 59 | L4 | lo=6 hi=6 size=1 | Enter mergeSort(arr, 6, 6) on 1 element. |
| 60 | L2 | lo=6 hi=6 | Base case: one element is already sorted. |
| 61 | L5 | lo=7 hi=7 size=1 | Enter mergeSort(arr, 7, 7) on 1 element. |
| 62 | L2 | lo=7 hi=7 | Base case: one element is already sorted. |
| 63 | L10 | lo=6 mid=6 hi=7 i=6 j=7 k=0 | Allocate tmp for 2 elements. Left run [6..6] and right run [7..7] are each already sorted. |
| 64 | L13 | lo=6 mid=6 hi=7 i=6 j=7 k=0 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 65 | L13 | lo=6 mid=6 hi=7 i=7 j=7 k=1 | tmp[0] = 10. |
| 66 | L17 | lo=6 mid=6 hi=7 i=7 j=8 k=2 | The right run still has 1 element. They are already sorted and no larger run remains, so copy them all. |
| 67 | L18 | lo=6 mid=6 hi=7 i=7 j=8 k=2 | Copy tmp back into arr[6..7]. That range is now sorted. |
| 68 | L10 | lo=4 mid=5 hi=7 i=4 j=6 k=0 | Allocate tmp for 4 elements. Left run [4..5] and right run [6..7] are each already sorted. |
| 69 | L13 | lo=4 mid=5 hi=7 i=4 j=6 k=0 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 70 | L13 | lo=4 mid=5 hi=7 i=5 j=6 k=1 | tmp[0] = 9. |
| 71 | L13 | lo=4 mid=5 hi=7 i=5 j=6 k=1 | The right head is strictly smaller, so take it. |
| 72 | L14 | lo=4 mid=5 hi=7 i=5 j=7 k=2 | tmp[1] = 10. |
| 73 | L13 | lo=4 mid=5 hi=7 i=5 j=7 k=2 | The right head is strictly smaller, so take it. |
| 74 | L14 | lo=4 mid=5 hi=7 i=5 j=8 k=3 | tmp[2] = 15. |
| 75 | L16 | lo=4 mid=5 hi=7 i=6 j=8 k=4 | The left run still has 1 element. They are already sorted and no larger run remains, so copy them all. |
| 76 | L18 | lo=4 mid=5 hi=7 i=6 j=8 k=4 | Copy tmp back into arr[4..7]. That range is now sorted. |
| 77 | L10 | lo=0 mid=3 hi=7 i=0 j=4 k=0 | Allocate tmp for 8 elements. Left run [0..3] and right run [4..7] are each already sorted. |
| 78 | L13 | lo=0 mid=3 hi=7 i=0 j=4 k=0 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 79 | L13 | lo=0 mid=3 hi=7 i=1 j=4 k=1 | tmp[0] = 3. |
| 80 | L13 | lo=0 mid=3 hi=7 i=1 j=4 k=1 | The right head is strictly smaller, so take it. |
| 81 | L14 | lo=0 mid=3 hi=7 i=1 j=5 k=2 | tmp[1] = 9. |
| 82 | L13 | lo=0 mid=3 hi=7 i=1 j=5 k=2 | The right head is strictly smaller, so take it. |
| 83 | L14 | lo=0 mid=3 hi=7 i=1 j=6 k=3 | tmp[2] = 10. |
| 84 | L13 | lo=0 mid=3 hi=7 i=1 j=6 k=3 | The right head is strictly smaller, so take it. |
| 85 | L14 | lo=0 mid=3 hi=7 i=1 j=7 k=4 | tmp[3] = 15. |
| 86 | L13 | lo=0 mid=3 hi=7 i=1 j=7 k=4 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 87 | L13 | lo=0 mid=3 hi=7 i=2 j=7 k=5 | tmp[4] = 27. |
| 88 | L13 | lo=0 mid=3 hi=7 i=2 j=7 k=5 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 89 | L13 | lo=0 mid=3 hi=7 i=3 j=7 k=6 | tmp[5] = 38. |
| 90 | L13 | lo=0 mid=3 hi=7 i=3 j=7 k=6 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 91 | L13 | lo=0 mid=3 hi=7 i=4 j=7 k=7 | tmp[6] = 43. |
| 92 | L17 | lo=0 mid=3 hi=7 i=4 j=8 k=8 | The right run still has 1 element. They are already sorted and no larger run remains, so copy them all. |
| 93 | L18 | lo=0 mid=3 hi=7 i=4 j=8 k=8 | Copy tmp back into arr[0..7]. That range is now sorted. |
| 94 | L6 | Sorted with 17 comparisons. About n log n comparisons, whatever the input order. |
Merge procedure
Merging keeps one pointer at the head of each sorted run, i and j. Compare the two heads, copy the smaller into tmp, and advance that pointer. When one run runs out, copy the remainder of the other, since it is already sorted.
Merging two runs of sizes a and b takes at most a + b - 1 comparisons and exactly a + b writes. That linear cost is what makes the whole algorithm O(n log n).
For n = 8 the worst case is n⌈log₂ n⌉ - 2^⌈log₂ n⌉ + 1 = 17 comparisons. The default animation input reaches exactly that.
| Step | Left run head | Right run head | Action |
|---|---|---|---|
| 1 | 3 | 9 | 3 <= 9, take 3 |
| 2 | 27 | 9 | 27 > 9, take 9 |
| 3 | 27 | 10 | 27 > 10, take 10 |
| 4 | 27 | 15 | 27 > 15, take 15 |
| 5 | 27 | (empty) | right run empty, copy 27, 38, 43 |
Stable sorting
Merge sort is stable if, on ties, the element from the left run is taken first. That is the <= in arr[i] <= arr[j]. Elements from the left run came earlier in the input, so equal keys keep their original order.
Writing < there still sorts correctly but silently destroys stability. Run the “Duplicates” preset and check that the a/b tags stay in order.
Temporary arrays
Allocating a new tmp array inside every merge creates O(n) garbage per level. Allocate one buffer of size n up front and reuse it in every merge call. Space stays O(n), plus O(log n) recursion stack.
Two further optimisations are worth knowing. If arr[mid] <= arr[mid + 1] the halves are already in order, so skip the merge (this makes already sorted input O(n)). And for small ranges, insertion sort is faster than recursing.
1public static void mergeSort(int[] arr) {2int[] buffer = new int[arr.length]; // allocated once, reused by every merge3sort(arr, buffer, 0, arr.length - 1);4}56private static void sort(int[] arr, int[] buf, int lo, int hi) {7if (lo >= hi) return;8int mid = lo + (hi - lo) / 2;9sort(arr, buf, lo, mid);10sort(arr, buf, mid + 1, hi);11if (arr[mid] <= arr[mid + 1]) return; // already in order: skip the merge12merge(arr, buf, lo, mid, hi);13}1415private static void merge(int[] arr, int[] buf, int lo, int mid, int hi) {16System.arraycopy(arr, lo, buf, lo, hi - lo + 1);17int i = lo, j = mid + 1;18for (int k = lo; k <= hi; k++) {19if (i > mid) arr[k] = buf[j++];20else if (j > hi) arr[k] = buf[i++];21else if (buf[j] < buf[i]) arr[k] = buf[j++]; // strict <: ties take the left element22else arr[k] = buf[i++];23}24}
Bottom-up merge sort
Bottom-up merge sort removes the recursion. Treat the array as n runs of width 1, merge neighbouring pairs into runs of width 2, then 4, then 8, and so on until one run remains.
It performs the same merges as the top-down version when n is a power of two, uses no call stack, and is a natural fit for sorting data that arrives in runs. The last run of a round may be shorter, so clamp hi to n - 1.
Best
O(n log n)
Average
O(n log n)
Worst
O(n log n)
Space
O(n)
Merge sort (bottom-up)
Interactive · edit the inputint[] arr · length 8
No recursion: start with runs of width 1 and merge neighbouring pairs, doubling the width each round.
1public static void mergeSortBottomUp(int[] arr) {2int n = arr.length;3for (int width = 1; width < n; width *= 2) {4for (int lo = 0; lo < n - width; lo += 2 * width) {5int mid = lo + width - 1;6int hi = Math.min(lo + 2 * width - 1, n - 1);7merge(arr, lo, mid, hi);8}9}10}1112static void merge(int[] arr, int lo, int mid, int hi) {13int[] tmp = new int[hi - lo + 1];14int i = lo, j = mid + 1, k = 0;15while (i <= mid && j <= hi) {16if (arr[i] <= arr[j]) tmp[k++] = arr[i++];17else tmp[k++] = arr[j++];18}19while (i <= mid) tmp[k++] = arr[i++];20while (j <= hi) tmp[k++] = arr[j++];21System.arraycopy(tmp, 0, arr, lo, tmp.length);22}
Live state
n
8
Execution trace (dry run table)Show 67 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | n=8 | No recursion: start with runs of width 1 and merge neighbouring pairs, doubling the width each round. |
| 2 | L3 | width=1 | Width 1: merge adjacent runs of 1 element. |
| 3 | L6 | width=1 lo=0 mid=0 hi=1 | Merge [0..0] with [1..1]. |
| 4 | L13 | lo=0 mid=0 hi=1 i=0 j=1 k=0 | Allocate tmp for 2 elements. Left run [0..0] and right run [1..1] are each already sorted. |
| 5 | L16 | lo=0 mid=0 hi=1 i=0 j=1 k=0 | The right head is strictly smaller, so take it. |
| 6 | L17 | lo=0 mid=0 hi=1 i=0 j=2 k=1 | tmp[0] = 27. |
| 7 | L19 | lo=0 mid=0 hi=1 i=1 j=2 k=2 | The left run still has 1 element. They are already sorted and no larger run remains, so copy them all. |
| 8 | L21 | lo=0 mid=0 hi=1 i=1 j=2 k=2 | Copy tmp back into arr[0..1]. That range is now sorted. |
| 9 | L6 | width=1 lo=2 mid=2 hi=3 | Merge [2..2] with [3..3]. |
| 10 | L13 | lo=2 mid=2 hi=3 i=2 j=3 k=0 | Allocate tmp for 2 elements. Left run [2..2] and right run [3..3] are each already sorted. |
| 11 | L16 | lo=2 mid=2 hi=3 i=2 j=3 k=0 | The right head is strictly smaller, so take it. |
| 12 | L17 | lo=2 mid=2 hi=3 i=2 j=4 k=1 | tmp[0] = 3. |
| 13 | L19 | lo=2 mid=2 hi=3 i=3 j=4 k=2 | The left run still has 1 element. They are already sorted and no larger run remains, so copy them all. |
| 14 | L21 | lo=2 mid=2 hi=3 i=3 j=4 k=2 | Copy tmp back into arr[2..3]. That range is now sorted. |
| 15 | L6 | width=1 lo=4 mid=4 hi=5 | Merge [4..4] with [5..5]. |
| 16 | L13 | lo=4 mid=4 hi=5 i=4 j=5 k=0 | Allocate tmp for 2 elements. Left run [4..4] and right run [5..5] are each already sorted. |
| 17 | L16 | lo=4 mid=4 hi=5 i=4 j=5 k=0 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 18 | L16 | lo=4 mid=4 hi=5 i=5 j=5 k=1 | tmp[0] = 9. |
| 19 | L20 | lo=4 mid=4 hi=5 i=5 j=6 k=2 | The right run still has 1 element. They are already sorted and no larger run remains, so copy them all. |
| 20 | L21 | lo=4 mid=4 hi=5 i=5 j=6 k=2 | Copy tmp back into arr[4..5]. That range is now sorted. |
| 21 | L6 | width=1 lo=6 mid=6 hi=7 | Merge [6..6] with [7..7]. |
| 22 | L13 | lo=6 mid=6 hi=7 i=6 j=7 k=0 | Allocate tmp for 2 elements. Left run [6..6] and right run [7..7] are each already sorted. |
| 23 | L16 | lo=6 mid=6 hi=7 i=6 j=7 k=0 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 24 | L16 | lo=6 mid=6 hi=7 i=7 j=7 k=1 | tmp[0] = 10. |
| 25 | L20 | lo=6 mid=6 hi=7 i=7 j=8 k=2 | The right run still has 1 element. They are already sorted and no larger run remains, so copy them all. |
| 26 | L21 | lo=6 mid=6 hi=7 i=7 j=8 k=2 | Copy tmp back into arr[6..7]. That range is now sorted. |
| 27 | L3 | width=2 | Width 2: merge adjacent runs of 2 elements. |
| 28 | L6 | width=2 lo=0 mid=1 hi=3 | Merge [0..1] with [2..3]. |
| 29 | L13 | lo=0 mid=1 hi=3 i=0 j=2 k=0 | Allocate tmp for 4 elements. Left run [0..1] and right run [2..3] are each already sorted. |
| 30 | L16 | lo=0 mid=1 hi=3 i=0 j=2 k=0 | The right head is strictly smaller, so take it. |
| 31 | L17 | lo=0 mid=1 hi=3 i=0 j=3 k=1 | tmp[0] = 3. |
| 32 | L16 | lo=0 mid=1 hi=3 i=0 j=3 k=1 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 33 | L16 | lo=0 mid=1 hi=3 i=1 j=3 k=2 | tmp[1] = 27. |
| 34 | L16 | lo=0 mid=1 hi=3 i=1 j=3 k=2 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 35 | L16 | lo=0 mid=1 hi=3 i=2 j=3 k=3 | tmp[2] = 38. |
| 36 | L20 | lo=0 mid=1 hi=3 i=2 j=4 k=4 | The right run still has 1 element. They are already sorted and no larger run remains, so copy them all. |
| 37 | L21 | lo=0 mid=1 hi=3 i=2 j=4 k=4 | Copy tmp back into arr[0..3]. That range is now sorted. |
| 38 | L6 | width=2 lo=4 mid=5 hi=7 | Merge [4..5] with [6..7]. |
| 39 | L13 | lo=4 mid=5 hi=7 i=4 j=6 k=0 | Allocate tmp for 4 elements. Left run [4..5] and right run [6..7] are each already sorted. |
| 40 | L16 | lo=4 mid=5 hi=7 i=4 j=6 k=0 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 41 | L16 | lo=4 mid=5 hi=7 i=5 j=6 k=1 | tmp[0] = 9. |
| 42 | L16 | lo=4 mid=5 hi=7 i=5 j=6 k=1 | The right head is strictly smaller, so take it. |
| 43 | L17 | lo=4 mid=5 hi=7 i=5 j=7 k=2 | tmp[1] = 10. |
| 44 | L16 | lo=4 mid=5 hi=7 i=5 j=7 k=2 | The right head is strictly smaller, so take it. |
| 45 | L17 | lo=4 mid=5 hi=7 i=5 j=8 k=3 | tmp[2] = 15. |
| 46 | L19 | lo=4 mid=5 hi=7 i=6 j=8 k=4 | The left run still has 1 element. They are already sorted and no larger run remains, so copy them all. |
| 47 | L21 | lo=4 mid=5 hi=7 i=6 j=8 k=4 | Copy tmp back into arr[4..7]. That range is now sorted. |
| 48 | L3 | width=4 | Width 4: merge adjacent runs of 4 elements. |
| 49 | L6 | width=4 lo=0 mid=3 hi=7 | Merge [0..3] with [4..7]. |
| 50 | L13 | lo=0 mid=3 hi=7 i=0 j=4 k=0 | Allocate tmp for 8 elements. Left run [0..3] and right run [4..7] are each already sorted. |
| 51 | L16 | lo=0 mid=3 hi=7 i=0 j=4 k=0 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 52 | L16 | lo=0 mid=3 hi=7 i=1 j=4 k=1 | tmp[0] = 3. |
| 53 | L16 | lo=0 mid=3 hi=7 i=1 j=4 k=1 | The right head is strictly smaller, so take it. |
| 54 | L17 | lo=0 mid=3 hi=7 i=1 j=5 k=2 | tmp[1] = 9. |
| 55 | L16 | lo=0 mid=3 hi=7 i=1 j=5 k=2 | The right head is strictly smaller, so take it. |
| 56 | L17 | lo=0 mid=3 hi=7 i=1 j=6 k=3 | tmp[2] = 10. |
| 57 | L16 | lo=0 mid=3 hi=7 i=1 j=6 k=3 | The right head is strictly smaller, so take it. |
| 58 | L17 | lo=0 mid=3 hi=7 i=1 j=7 k=4 | tmp[3] = 15. |
| 59 | L16 | lo=0 mid=3 hi=7 i=1 j=7 k=4 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 60 | L16 | lo=0 mid=3 hi=7 i=2 j=7 k=5 | tmp[4] = 27. |
| 61 | L16 | lo=0 mid=3 hi=7 i=2 j=7 k=5 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 62 | L16 | lo=0 mid=3 hi=7 i=3 j=7 k=6 | tmp[5] = 38. |
| 63 | L16 | lo=0 mid=3 hi=7 i=3 j=7 k=6 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 64 | L16 | lo=0 mid=3 hi=7 i=4 j=7 k=7 | tmp[6] = 43. |
| 65 | L20 | lo=0 mid=3 hi=7 i=4 j=8 k=8 | The right run still has 1 element. They are already sorted and no larger run remains, so copy them all. |
| 66 | L21 | lo=0 mid=3 hi=7 i=4 j=8 k=8 | Copy tmp back into arr[0..7]. That range is now sorted. |
| 67 | L10 | Sorted with 17 comparisons in 3 rounds. |
Linked list merge sort
Merge sort is the standard way to sort a linked list. Arrays make splitting free but merging needs a buffer; lists are the opposite: finding the middle costs O(n) (slow and fast pointers), but merging just relinks nodes, so no extra buffer is needed.
Quick sort and heap sort depend on random access, which lists do not have, so merge sort wins here. Extra space is only the O(log n) recursion stack.
1static ListNode sortList(ListNode head) {2if (head == null || head.next == null) return head;3ListNode slow = head, fast = head.next; // find the middle4while (fast != null && fast.next != null) {5slow = slow.next;6fast = fast.next.next;7}8ListNode right = slow.next;9slow.next = null; // cut the list in two10return merge(sortList(head), sortList(right));11}1213static ListNode merge(ListNode a, ListNode b) {14ListNode dummy = new ListNode(0), tail = dummy;15while (a != null && b != null) {16if (b.val < a.val) { tail.next = b; b = b.next; } // ties keep the left node17else { tail.next = a; a = a.next; }18tail = tail.next;19}20tail.next = (a != null) ? a : b;21return dummy.next;22}
Worst
O(n log n)
Space
O(log n) stack
Practice ladder
Now try it yourself
Easy
- Merge two sorted arrays into a new array
- Merge sorted array (merge nums2 into nums1 from the back)
- Check whether an array is sorted in O(n)
Medium
- Sort a linked list in O(n log n)
- Merge k sorted lists
- Sort an array of strings by length, stably
Advanced
- Count inversions during the merge
- In-place merge in O(1) extra space and discuss its cost
- Natural merge sort that exploits existing runs
Key takeaways
- Merge sort is O(n log n) for every input, stable, and needs O(n) extra space for arrays.
- Take from the left run on ties to stay stable; reuse one buffer instead of allocating per merge.
- Bottom-up avoids recursion; on linked lists merge sort needs no buffer at all.