75. Sort Colors
Problem Statement: You are given an array holding only the values
0,1and2. Sort it in place so all the0s come first, then the1s, then the2s. Do it in a single pass, without calling a sorting function.
Input: nums = [2, 0, 2, 1, 1, 0]
Output: [0, 0, 1, 1, 2, 2]
Picture it
With only three possible values, the finished array is three blocks: zeros, then ones, then twos.
So the array can be thought of as four regions while the work is going on:
[ settled zeros | settled ones | not looked at yet | settled twos ]
Three markers hold those regions apart. low is the first slot after the zeros. high is the last slot before the twos. i is the scanner, walking through the unexamined middle.
Every value the scanner meets gets sent to one of the three blocks, and the unexamined middle shrinks from both sides. When it disappears, the array is sorted.
How to think about it
The scanner looks at nums[i] and does one of three things.
- It is a
0. It belongs at the front, so swap it with whatever is atlow, then movelowandiboth forward. - It is a
1. It is already in the middle block where it belongs, so just moveiforward. - It is a
2. It belongs at the back, so swap it with whatever is athighand movehighbackwards.
Look carefully at that last case. i does not move. The value that just arrived from the back has never been examined, so the scanner has to look at it next. Advancing past it is the single most common way to get this wrong, and it silently leaves stray values in the middle.
The zero case is different, and it is fair to ask why i can safely move there. Everything to the left of i has already been examined, so the value swapped up from low is something the scanner has seen before, and it can only be a 1. Moving over it loses nothing.
The loop runs while i has not passed high. Once it does, there is no unexamined region left.
Walking through the example
Take nums = [2, 0, 2, 1, 1, 0], with low = 0, i = 0, high = 5.
nums[0]is2. Swap with position5:[0, 0, 2, 1, 1, 2]. Nowhigh = 4, andistays at0.nums[0]is0. Swap with position0, which changes nothing. Nowlow = 1,i = 1.nums[1]is0. Again a self-swap. Nowlow = 2,i = 2.nums[2]is2. Swap with position4:[0, 0, 1, 1, 2, 2]. Nowhigh = 3, andistays at2.nums[2]is1. Leave it,i = 3.nums[3]is1. Leave it,i = 4. That is pasthigh, so the sweep ends.
The array is [0, 0, 1, 1, 2, 2].
The code
class Solution:
def sortColors(self, nums: list[int]) -> None:
low, i, high = 0, 0, len(nums) - 1
while i <= high:
if nums[i] == 0:
nums[low], nums[i] = nums[i], nums[low]
low += 1
i += 1
elif nums[i] == 2:
nums[high], nums[i] = nums[i], nums[high]
high -= 1
# i stays put: the value from the back has not been examined
else:
i += 1
Time and space complexity
Time:
Each pass of the loop either moves i forward or moves high backward, so the gap between them shrinks every single time. It starts at n and the loop ends when it closes, which caps the number of passes at n.
That is what "single pass" means here, even though i sometimes stands still.
Space:
Three indices, and every move is a swap inside the array.
Counting the three values and then rewriting the array from those counts is the other classic answer. It is simple and also , but it reads the array twice, which this problem explicitly rules out.
Remember this
Three markers: zeros settle below low, twos settle above high, and i scans the middle. Swap a 0 down and advance both, leave a 1 alone, swap a 2 up and do not advance i, because the value that came back from the end has not been looked at yet. time, space, one pass.