Topics › Arrays › Medium

Sort an array of 0's 1's and 2's

Medium TUF ID 36 SDE

Problem

You are given an array nums whose values are only 0, 1 and 2. Sort it in place so that all 0s come first, then all 1s, then all 2s. The intended solution does not call a library sort. (On LeetCode this is “Sort Colors”: red, white and blue are encoded as 0, 1, 2.)

Constraints: 1 ≤ n ≤ 300 on LeetCode, but the techniques below are meant for arrays of any size, and the Java file also handles the empty array.

Example 1

nums = [2, 0, 2, 1, 1, 0]
after = [0, 0, 1, 1, 2, 2]

Example 2

nums = [2, 0, 1]
after = [0, 1, 2]        // each value appears once

The follow-up question is the interesting part: can you do it in one pass with constant extra space?

Approach 1: Brute force, use a general sort

Intuition. It is a sorting problem, so any comparison sort works. This ignores the most useful fact we have, that there are only three distinct values, and so it pays the general O(n log n) price.

Algorithm.

  1. Call Arrays.sort(nums).
static void bruteForce(int[] nums) {
    Arrays.sort(nums);
}

Complexity. Time O(n log n). Space O(log n) for the sort’s recursion on primitive arrays.

In an interview this is a starting point to mention, not an answer.

Approach 2: Better, counting

Intuition. With only three possible values, the sorted array is fully described by three numbers: how many 0s, 1s and 2s there are. Count them in one pass, then overwrite the array: the first cnt[0] slots with 0, the next cnt[1] with 1, and the rest with 2. This is counting sort with a three-bucket table.

Algorithm.

  1. Count occurrences of each value into cnt[0..2].
  2. Walk v = 0, 1, 2 and write v exactly cnt[v] times, advancing a write index k.
static void better(int[] nums) {
    int[] cnt = new int[3];
    for (int v : nums) cnt[v]++;
    int k = 0;
    for (int v = 0; v < 3; v++) {
        for (int c = 0; c < cnt[v]; c++) nums[k++] = v;
    }
}

Complexity. Time O(n), two passes over the array. Space O(1), a fixed array of three counters.

This is already linear and is perfectly acceptable. The only thing it does not achieve is the one-pass follow-up, and it rewrites values rather than moving the original elements, which matters if the elements carry other data (for example, objects keyed by colour).

Approach 3: Optimal, Dutch National Flag

Intuition. Edsger Dijkstra’s Dutch National Flag algorithm partitions the array into three regions with three pointers, while a single scan shrinks the unknown middle part. At every moment:

[0, low)       all 0s
[low, mid)     all 1s
[mid, high]    not examined yet
(high, n)      all 2s

Look at nums[mid], the first unexamined element:

The loop ends when the unknown region is empty, that is, when mid > high.

Algorithm.

  1. low = 0, mid = 0, high = n - 1.
  2. While mid ≤ high, apply the three cases above.
static void optimal(int[] nums) {
    int low = 0, mid = 0, high = nums.length - 1;
    // [0, low) are 0s, [low, mid) are 1s, [mid, high] unknown, (high, n) are 2s
    while (mid <= high) {
        if (nums[mid] == 0) {
            swap(nums, low++, mid++);
        } else if (nums[mid] == 1) {
            mid++;
        } else {
            swap(nums, mid, high--);       // do not advance mid: the swapped-in value is unexamined
        }
    }
}

static void swap(int[] a, int i, int j) {
    int t = a[i];
    a[i] = a[j];
    a[j] = t;
}

Dry run on nums = [2, 0, 2, 1, 1, 0]:

step nums low mid high action
0 [2, 0, 2, 1, 1, 0] 0 0 5 2: swap mid with high
1 [0, 0, 2, 1, 1, 2] 0 0 4 0: swap mid with low
2 [0, 0, 2, 1, 1, 2] 1 1 4 0: swap mid with low
3 [0, 0, 2, 1, 1, 2] 2 2 4 2: swap mid with high
4 [0, 0, 1, 1, 2, 2] 2 2 3 1: mid++
5 [0, 0, 1, 1, 2, 2] 2 3 3 1: mid++
6 [0, 0, 1, 1, 2, 2] 2 4 3 mid > high, stop

Complexity. Time O(n): every iteration either advances mid or shrinks high, so the unknown region loses one element per iteration and there are at most n iterations. Space O(1).

Why it is correct. The four-region picture is an invariant: it holds before the loop (three regions are empty and everything is unknown) and each case preserves it. When the unknown region is empty, the remaining three regions are exactly the sorted order.

Edge cases and pitfalls