Problem Walkthrough

Sort Colors LeetCode 75 Guide

Solve LeetCode 75 Sort Colors using the Dutch National Flag algorithm — a one-pass, in-place three-way partitioning technique invented by Dijkstra that uses three pointers to sort an array of 0s, 1s, and 2s in O(n) time with O(1) space, without any extra counting pass.

8 min read|

Sort Colors

LeetCode 75

Problem Overview

LeetCode 75 — Sort Colors — presents an array nums containing only the values 0, 1, and 2, representing the colors red, white, and blue respectively. The task is to sort the array in-place so that all 0s appear first, all 1s appear second, and all 2s appear last — matching the order of the Dutch national flag. You must not use any library sorting function.

The problem has a straightforward two-pass solution using counting sort: count the occurrences of 0, 1, and 2 in the first pass, then overwrite the array in the second pass using those counts. This runs in O(n) time and O(1) space and is perfectly correct. However, the follow-up challenge asks whether you can solve the problem in a single pass — and that is where the Dutch National Flag algorithm comes in.

This problem is a foundational array partitioning exercise. It tests your ability to maintain invariants while manipulating pointer positions and performing in-place swaps. The same three-way partitioning technique is the basis for the three-way quicksort partition, the Dutch National Flag variant of quicksort for arrays with many duplicate keys, and related problems like kth largest element.

  • Input: array nums with values only 0, 1, or 2 (representing red, white, blue)
  • Output: array sorted in-place with all 0s first, then 1s, then 2s
  • Must sort in-place — no extra array allowed
  • Must not use built-in sort functions
  • Follow-up: solve in one pass with O(1) extra space
  • Constraints: 1 <= nums.length <= 300, nums[i] is 0, 1, or 2

Why Counting Sort Isn't Enough

The two-pass counting sort solution is simple and correct: iterate the array once to count 0s, 1s, and 2s; then overwrite the array filling count0 zeros, then count1 ones, then count2 twos. This is O(n) time and O(1) space and solves the problem completely. For most interview contexts, presenting this solution first and then improving to one-pass is the ideal approach.

The limitation of counting sort is that it requires two passes over the data — one to gather counts and one to rewrite values. The follow-up challenge specifically asks whether you can sort the array in a single traversal without any pre-counting phase. The Dutch National Flag algorithm achieves this by maintaining three running pointers that collectively enforce a four-region invariant as they sweep through the array once.

Understanding why one-pass matters in practice: in streaming scenarios where data arrives element by element and you cannot buffer it for a second scan, one-pass algorithms are strictly necessary. In interview settings, the one-pass constraint demonstrates mastery of pointer manipulation and loop invariant reasoning — skills that transfer directly to partition-based algorithms in quicksort and related problems.

💡

The Dutch National Flag Algorithm Is Dijkstra's Classic

The Dutch National Flag algorithm was formulated by Edsger W. Dijkstra as an exercise in program correctness and loop invariant reasoning. It partitions an array into three color groups using three pointers that maintain four invariant regions throughout a single pass. The algorithm is a textbook example of how precise invariant specification leads directly to a clean, provably correct implementation — a technique Dijkstra championed throughout his career.

Three Pointer Setup

The Dutch National Flag algorithm uses three integer pointers: low, mid, and high. Initialize low = 0, mid = 0, and high = n - 1 where n is the length of the array. The algorithm proceeds while mid <= high, performing swaps and pointer moves based on the value at nums[mid].

The invariant maintained throughout the algorithm divides the array into four regions at all times. Elements in indices [0, low-1] are guaranteed to be 0s. Elements in indices [low, mid-1] are guaranteed to be 1s. Elements in indices [mid, high] are the unprocessed region whose final positions are not yet determined. Elements in indices [high+1, n-1] are guaranteed to be 2s. When mid passes high, the unprocessed region is empty and the array is fully sorted.

This four-region invariant is the core of the algorithm. Every swap and pointer movement is chosen specifically to preserve or advance the invariant. When mid crosses high, all four regions are non-empty only for their correct values — the sort is complete without any second pass over the data.

  1. 1Initialize: low = 0, mid = 0, high = n - 1
  2. 2Invariant region 1: nums[0..low-1] contains only 0s (sorted)
  3. 3Invariant region 2: nums[low..mid-1] contains only 1s (sorted)
  4. 4Invariant region 3: nums[mid..high] contains unprocessed elements
  5. 5Invariant region 4: nums[high+1..n-1] contains only 2s (sorted)
  6. 6Termination: when mid > high, unprocessed region is empty — array is sorted

Processing Rules

The algorithm processes nums[mid] on each iteration with three cases. Case 1: if nums[mid] == 0, swap nums[mid] with nums[low], then increment both low and mid. The 0 is now in its correct region, and the element that was at low (which we know is a 1 since it was in the [low, mid-1] region) has been moved to mid and is still correctly classified, so mid can safely advance.

Case 2: if nums[mid] == 1, simply increment mid without any swap. The element is already in the correct 1s region between low and mid, so advancing mid extends the sorted 1s region by one position with no work required.

Case 3: if nums[mid] == 2, swap nums[mid] with nums[high], then decrement high only — do not increment mid. The 2 is now in its correct region at the high end. However, the element that was at high and is now at mid is unknown — it could be a 0, 1, or 2 — so mid must stay in place to classify that newly revealed element on the next iteration.

ℹ️

Why Mid Advances After Swapping With Low But Not After Swapping With High

When swapping nums[mid] with nums[low], we know the element coming from low is either a 0 or a 1 — it was already in the processed left region. Since it is not a 2, we can safely advance mid. When swapping nums[mid] with nums[high], the element coming from high is completely unknown — it has not been examined yet. We must keep mid pointing at it so the next iteration can classify it correctly. This asymmetry is the subtle but critical difference between the two swap cases.

Walk-Through Example

Trace the algorithm on nums = [2, 0, 2, 1, 1, 0]. Initial state: low = 0, mid = 0, high = 5. nums[mid] = nums[0] = 2, so swap with high: swap(0, 5) → [0, 0, 2, 1, 1, 2], then high = 4. Now mid = 0, low = 0, high = 4. nums[mid] = 0, so swap with low: swap(0, 0) → [0, 0, 2, 1, 1, 2] (no change), then low = 1, mid = 1.

Continue: mid = 1, low = 1, high = 4. nums[mid] = 0, so swap with low: swap(1, 1) → [0, 0, 2, 1, 1, 2] (no change), low = 2, mid = 2. Now mid = 2, low = 2, high = 4. nums[mid] = 2, so swap with high: swap(2, 4) → [0, 0, 1, 1, 2, 2], then high = 3. mid = 2, low = 2, high = 3.

Continue: nums[mid] = nums[2] = 1, so just mid = 3. Now mid = 3, low = 2, high = 3. nums[mid] = 1, so just mid = 4. Now mid = 4 > high = 3, loop ends. Final array: [0, 0, 1, 1, 2, 2]. The algorithm performed 5 iterations and 2 actual swaps to fully sort the array.

  1. 1Start: [2,0,2,1,1,0] low=0 mid=0 high=5
  2. 2nums[0]=2: swap(0,5) → [0,0,2,1,1,2], high=4
  3. 3nums[0]=0: swap(0,0) → [0,0,2,1,1,2], low=1 mid=1
  4. 4nums[1]=0: swap(1,1) → [0,0,2,1,1,2], low=2 mid=2
  5. 5nums[2]=2: swap(2,4) → [0,0,1,1,2,2], high=3
  6. 6nums[2]=1: mid=3
  7. 7nums[3]=1: mid=4 → mid > high, done. Result: [0,0,1,1,2,2]

Code Walkthrough: Python and Java

Python solution: def sortColors(nums): low, mid, high = 0, 0, len(nums) - 1; while mid <= high: if nums[mid] == 0: nums[low], nums[mid] = nums[mid], nums[low]; low += 1; mid += 1; elif nums[mid] == 1: mid += 1; else: nums[mid], nums[high] = nums[high], nums[mid]; high -= 1. This is the complete solution — roughly 10 lines including the function signature. Time complexity O(n), space O(1).

Java solution: public void sortColors(int[] nums) { int low = 0, mid = 0, high = nums.length - 1; while (mid <= high) { if (nums[mid] == 0) { int tmp = nums[low]; nums[low++] = nums[mid]; nums[mid++] = tmp; } else if (nums[mid] == 1) { mid++; } else { int tmp = nums[high]; nums[high--] = nums[mid]; nums[mid] = tmp; } } }. Both solutions are concise, in-place, and handle edge cases (all same color, already sorted, reverse sorted) correctly without modification.

The algorithm always terminates because on every iteration, either mid increases (Cases 1 and 2) or high decreases (Case 3), so the size of the unprocessed region [mid, high] strictly decreases by 1 on each step. The total number of iterations is at most n, confirming O(n) time complexity. The number of swaps is at most n - 1, which is also optimal for an in-place sort of this problem.

⚠️

The Loop Condition Is mid <= high, Not mid < high

The while loop must use mid <= high, not mid < high. When mid equals high, the element at that single position is still unprocessed and must be classified. If you use mid < high, the last element in the unprocessed region is never examined — leaving the array potentially unsorted at the boundary. This off-by-one error is the most common implementation mistake with the Dutch National Flag algorithm. Always include the mid == high case in the loop.

Ready to master algorithm patterns?

YeetCode flashcards help you build pattern recognition through active recall and spaced repetition.

Start practicing now