Medium20–25 min
Topics
Companies
Practice:GFG ↗
Given an array of integers, sort it in non-decreasing order using **Merge Sort** — repeatedly split the array in half, sort each half, then merge the two sorted halves back together. This is the classic divide-and-conquer sort: splitting always takes O(log n) levels, and merging two sorted halves takes O(n) work at each level, giving O(n log n) overall — reliably faster than the simple O(n²) sorts on large inputs, and (unlike quicksort) with no bad-input case that degrades it.

Test Case 1:

Input:arr = [7, 2, 9, 4, 2, 8]
Output:[2, 2, 4, 7, 8, 9]
Explanation:A typical unsorted array with a repeated value.

Test Case 2:

Input:arr = []
Output:[]
Explanation:An empty array is already sorted.

Test Case 3:

Input:arr = [5]
Output:[5]
Explanation:A single element is already sorted.

Constraints

  • 0 ≤ arr.length ≤ 200
  • -1000 ≤ arr[i] ≤ 1000
  • Sort in non-decreasing order
🚀

Try the Dry Run

Don't just read the solution — watch it execute, one step at a time.

Approach & Solutions

Brute Force — Fresh Array at Every Merge

Good

Split the array in half, recursively sort each half, then merge the two sorted halves back together. The natural way to write this splits by slicing out brand-new left and right arrays at every single recursive call, and merge() builds yet another new array to hold the result. It's correct and easy to follow, but across all the levels of recursion, the total memory allocated for all those throwaway sub-arrays adds up to more than the O(n) a merge sort actually needs.

TimeO(n log n)
SpaceO(n log n)
1class Solution { 2 public int[] mergeSort(int[] arr) { 3 if (arr.length <= 1) return arr; 4 int mid = arr.length / 2; 5 int[] left = mergeSort(Arrays.copyOfRange(arr, 0, mid)); 6 int[] right = mergeSort(Arrays.copyOfRange(arr, mid, arr.length)); 7 return merge(left, right); 8 } 9 10 private int[] merge(int[] left, int[] right) { 11 int[] result = new int[left.length + right.length]; 12 int i = 0, j = 0, k = 0; 13 while (i < left.length && j < right.length) { 14 if (left[i] <= right[j]) { 15 result[k++] = left[i++]; 16 } else { 17 result[k++] = right[j++]; 18 } 19 } 20 while (i < left.length) result[k++] = left[i++]; 21 while (j < right.length) result[k++] = right[j++]; 22 return result; 23 } 24}

Optimal — One Reused Auxiliary Buffer

Optimal

Same divide-and-conquer idea, same time complexity — but allocate exactly one auxiliary array up front, and pass it down through every recursive call by reference. Recursion now works on index ranges (lo, hi) into the original array instead of slicing out new sub-arrays, and every merge copies its range into the shared aux buffer and merges straight back into arr in place. Only one extra array ever exists, no matter how deep the recursion goes.

TimeO(n log n)
SpaceO(n)
1class Solution { 2 public int[] mergeSort(int[] arr) { 3 int n = arr.length; 4 int[] aux = new int[n]; 5 sort(arr, aux, 0, n - 1); 6 return arr; 7 } 8 9 private void sort(int[] arr, int[] aux, int lo, int hi) { 10 if (lo >= hi) return; 11 int mid = lo + (hi - lo) / 2; 12 sort(arr, aux, lo, mid); 13 sort(arr, aux, mid + 1, hi); 14 merge(arr, aux, lo, mid, hi); 15 } 16 17 private void merge(int[] arr, int[] aux, int lo, int mid, int hi) { 18 for (int k = lo; k <= hi; k++) { 19 aux[k] = arr[k]; 20 } 21 int i = lo, j = mid + 1; 22 for (int k = lo; k <= hi; k++) { 23 if (i > mid) { 24 arr[k] = aux[j++]; 25 } else if (j > hi) { 26 arr[k] = aux[i++]; 27 } else if (aux[i] <= aux[j]) { 28 arr[k] = aux[i++]; 29 } else { 30 arr[k] = aux[j++]; 31 } 32 } 33 } 34}

Related Problems