LeetCode #75 Medium

Sort Colors

Sort Colors: sort an array of 0s, 1s, and 2s in place in a single pass (the Dutch National Flag problem).

Constraints
  • n == nums.length
  • 1 <= n <= 300
  • nums[i] is either 0, 1, or 2.
arraytwo-pointersdutch-flag
Open on LeetCode ↗
02

Intuition

Sort colors sorts an array containing only 0s, 1s, and 2s, in place and in a single pass. Counting each value and overwriting works in two passes; the one-pass solution is the Dutch National Flag algorithm. With only three distinct values, the array can be partitioned into three regions maintained simultaneously: - Three pointers divide the array — everything before low is 0, everything after high is 2, and the region between low and mid is 1. mid scans forward and each value it meets is handled by one rule. A 0 swaps to the low boundary, advancing both low and mid. A 1 is already correct, so only mid advances. A 2 swaps to the high boundary and high retreats — and crucially mid does not move. That asymmetry is the crux. After swapping with high, the value received came from unexamined territory and must be inspected. Advancing mid skips it, leaving 2s stranded in the middle. Swapping with low, by contrast, brings back an already-classified value, so advancing is safe. The loop runs while mid <= high, inclusive. Using < leaves the final element unclassified. Each element is examined at most twice — once by mid and possibly once after a swap — so the pass is genuinely linear despite the swapping. The counting-sort approach is simpler and often preferable in practice: tally the three values, then overwrite. Two passes, same O(n), and much harder to get wrong. The problem specifically asks for one pass, which is why the three-pointer version is the expected answer. One pass gives O(n) time and O(1) space.

How to spot this pattern

The Dutch national flag partition: three pointers carve the array into [0s | 1s | unknown | 2s]. The invariant is that everything left of low is 0, everything between low and mid is 1, and everything right of high is 2. Any "sort three distinct values in one pass" question is this pattern.

03

Approach

Try it first

Before reading on: counting the three values and rewriting works but touches the array twice. Ask what three pointers would need to mean for one pass to suffice — and which region each pointer bounds.

1

Note the three-value constraint

Only 0s, 1s, and 2s appear, so the array partitions into three regions rather than needing a general sort.

2

Maintain three pointers

Everything before low is 0, everything after high is 2, and mid scans the unclassified middle. The invariant holds throughout.

3

Swap zeros to the front

On a 0, swap with low and advance both low and mid — the value received is already classified, so moving on is safe.

4

Leave ones in place

A 1 belongs where it is, so only mid advances. No swap is needed.

5

Do not advance mid after a 2

Swapping with high brings back an unexamined value that must be inspected. Advancing mid skips it and strands 2s in the middle — the defining bug.

6

Use an inclusive loop bound

Run while mid <= high. Using < leaves the final element unclassified.

7

Cost of the approach

Each element is examined at most twice, giving O(n) time and O(1) space in a single pass, as the problem requires.

04

Solution & live demo

▶1class Solution:
▶2 def sortColors(self, nums):
▶3 low, mid, high = 0, 0, len(nums) - 1
▶4 while mid <= high:
▶5 if nums[mid] == 0:
▶6 nums[low], nums[mid] = nums[mid], nums[low]
▶7 low += 1
▶8 mid += 1
▶9 elif nums[mid] == 1:
▶10 mid += 1
▶11 else:
▶12 nums[mid], nums[high] = nums[high], nums[mid]
▶13 high -= 1
05

Common pitfalls

Advancing mid after swapping with high

✗ Wrong
else:
    nums[mid], nums[high] = nums[high], nums[mid]
    high -= 1
    mid += 1
✓ Right
else:
    nums[mid], nums[high] = nums[high], nums[mid]
    high -= 1

The value swapped in from high has never been examined — it could be a 0 or a 2. Advancing mid skips over it unclassified. Swapping with low is different: that value was already known to be a 1, so mid can safely move.

Looping while mid < high

✗ Wrong
while mid < high:
✓ Right
while mid <= high:

When mid == high there is still one unclassified element sitting at that index. Stopping early leaves it in place, so a final stray 0 or 2 stays unsorted.

Counting occurrences and rewriting

✗ Wrong
c = [nums.count(0), nums.count(1), nums.count(2)]
nums[:] = [0]*c[0] + [1]*c[1] + [2]*c[2]
✓ Right
low, mid, high = 0, 0, len(nums) - 1

Counting sort is correct but makes two passes, and the problem explicitly asks for a one-pass in-place solution. The three-pointer partition does it in a single sweep.

06

Edge cases

Already sorted, e.g. [0,1,2]

0 swaps with itself, 1 is skipped, 2 swaps with itself — order is preserved.

All identical values

All-0 advances both low and mid; all-2 shrinks high; all-1 just advances mid. Each terminates cleanly.

A 2 swapped to mid that is also a 2

Because mid does not advance on a 2-swap, the freshly swapped value is re-examined, never skipped.

07

Complexity

Time
O(n)
Space
O(1)
One pass, in place, three pointers.