#912 Sort an Array
the problem
In plain words
Sort an integer array in ascending order without built-in sort functions, in O(n log n) time and with the smallest space you can.
in nums = [5,2,3,1]
out [1,2,3,5]
in nums = [5,1,1,2,0,0]
out [0,0,1,1,2,5]
Duplicates must all be kept.
- 1 ≤ n ≤ 5·10⁴
- -5·10⁴ ≤ nums[i] ≤ 5·10⁴
your turn
Try it before you peek
Read the examples, say the brute force out loud, then write code: here or on LeetCode ↗. Stuck for 20 minutes? Open one hint at a time.
Solve it in the browser
LeetCode-style: complete the class. Compiled by the real javac (Java 17) and judged right here — nothing is sent anywhere.
Solutions are hidden so you can try first. Even 20 minutes of real effort makes the answer stick far better than reading it.
Marking the problem solved, or opening every hint, also shows it.
the aha
Key insight
patternMerge sort & merging sorted runsThree-way partition (Dutch flag)Counting / bucket sort
Any O(n²) sort times out at n = 5·10⁴. You need divide and conquer (merge/quick/heap) — or, because values fit in a range of 10⁵, counting sort.
solutions
From brute force to optimal
Grow a sorted prefix; slide each new value left into place.
- For i = 1..n−1, hold key = nums[i].
- Shift bigger values right.
- Drop key into the gap.
Reversed input needs n(n−1)/2 shifts — ~1.25·10⁹ at the max n. TLE.
Sorts in place.
class Solution { public int[] sortArray(int[] nums) { for (int i = 1; i < nums.length; i++) { int key = nums[i], j = i - 1; while (j >= 0 && nums[j] > key) nums[j + 1] = nums[j--]; nums[j + 1] = key; } return nums; // correct, but O(n²) → Time Limit Exceeded at n = 5·10⁴ }}Split in halves, sort each recursively, merge with two pointers.
- Allocate one helper array up front.
- Recursively sort [lo, mid] and [mid+1, hi].
- If a[mid] ≤ a[mid+1] the halves are already in order — skip the merge.
- Otherwise copy to aux and merge back, taking left on ties.
log n levels × O(n) merging per level, in every case.
The helper array plus O(log n) recursion.
class Solution { public int[] sortArray(int[] nums) { int[] aux = new int[nums.length]; sort(nums, aux, 0, nums.length - 1); return nums; } private void sort(int[] a, int[] aux, int lo, int hi) { if (lo >= hi) return; int mid = lo + (hi - lo) / 2; sort(a, aux, lo, mid); sort(a, aux, mid + 1, hi); if (a[mid] <= a[mid + 1]) return; // halves already in order → skip the merge for (int k = lo; k <= hi; k++) aux[k] = a[k]; int i = lo, j = mid + 1; for (int k = lo; k <= hi; k++) { if (i > mid) a[k] = aux[j++]; else if (j > hi) a[k] = aux[i++]; else if (aux[j] < aux[i]) a[k] = aux[j++]; else a[k] = aux[i++]; } }}Watch the merge
Random pivot, partition into < | = | >, recurse on the outer parts.
- Pick a random pivot.
- Dutch-flag partition with lt, i, gt.
- Recurse on [lo, lt−1] and [gt+1, hi].
Random pivots split well on average; the = block makes all-equal input O(n). Worst case O(n²) is astronomically unlikely.
Recursion depth of a balanced split.
class Solution { private final Random rnd = new Random(); public int[] sortArray(int[] nums) { sort(nums, 0, nums.length - 1); return nums; } private void sort(int[] a, int lo, int hi) { if (lo >= hi) return; int pivot = a[lo + rnd.nextInt(hi - lo + 1)]; // random pivot defeats sorted inputs int lt = lo, i = lo, gt = hi; // 3-way split defeats duplicates while (i <= gt) { if (a[i] < pivot) swap(a, lt++, i++); else if (a[i] > pivot) swap(a, i, gt--); else i++; } sort(a, lo, lt - 1); // a[lt..gt] all equal the pivot — already final sort(a, gt + 1, hi); } private void swap(int[] a, int i, int j) { int t = a[i]; a[i] = a[j]; a[j] = t; }}Partitioning (Lomuto, 2-way)
Build a max-heap in place, then repeatedly move the max to the end.
- Heapify bottom-up from n/2 − 1 down to 0.
- Swap root with the last heap slot, shrink the heap, sift down.
O(n) build + n sift-downs of O(log n).
The heap lives inside the array.
class Solution { public int[] sortArray(int[] nums) { int n = nums.length; for (int i = n / 2 - 1; i >= 0; i--) siftDown(nums, i, n); for (int end = n - 1; end > 0; end--) { int t = nums[0]; nums[0] = nums[end]; nums[end] = t; siftDown(nums, 0, end); } return nums; } private void siftDown(int[] a, int i, int size) { while (2 * i + 1 < size) { int c = 2 * i + 1; if (c + 1 < size && a[c + 1] > a[c]) c++; if (a[i] >= a[c]) return; int t = a[i]; a[i] = a[c]; a[c] = t; i = c; } }}Values lie in [−5·10⁴, 5·10⁴]: count each value, then write them back in order.
- count[x + 50000]++ for each x.
- Walk values low → high, writing each count times.
k = 100,001 possible values — linear for this input size.
The count array.
class Solution { public int[] sortArray(int[] nums) { final int OFFSET = 50_000; // constraint: -5·10⁴ ≤ nums[i] ≤ 5·10⁴ int[] count = new int[2 * OFFSET + 1]; for (int x : nums) count[x + OFFSET]++; int k = 0; for (int v = 0; v < count.length; v++) { while (count[v]-- > 0) nums[k++] = v - OFFSET; } return nums; }}in the room
How to say it in the interview
talk track
- Ask: can I use extra memory? Are there many duplicates? What's the value range?
- Pick merge sort as the safe default and say why: guaranteed O(n log n), stable.
- Mention quicksort's O(n²) trap on sorted/all-equal input and how a random pivot + 3-way partition fixes it.
- Point out that counting sort is legal here because the value range is bounded.
traps
- Lomuto quicksort with a fixed pivot TLEs on LeetCode's all-equal and sorted tests.
- Allocating a new array inside every merge call — correct but slow and memory hungry.
what they ask next
Follow-ups
Sort a 100 GB file with 1 GB of RAM?
External merge sort: sort 1 GB chunks in memory, write them out, then k-way merge the chunks with a min-heap.
Why does Java use different algorithms for int[] and Integer[]?
Objects need stability (equal keys may be distinguishable); primitives don't, so the faster unstable dual-pivot quicksort is used.
before you submit
Edge cases to test
- One element: already sorted, return it.
- 5·10⁴ equal values: a 2-way (Lomuto) quicksort goes O(n²) here — use a 3-way partition or merge sort.
- Sorted or reversed input: a fixed first/last pivot degrades to O(n²) with recursion n levels deep.
- Values at both bounds (−5·10⁴ and 5·10⁴): counting sort needs an offset of 50,000.
practise next
Similar problems
- #148Sort ListMediumin Sorting
- #75Sort ColorsMediumin Two Pointers
- #215Kth Largest Element in an ArrayMediumin Heaps