Array · 22 of 36
Three way partitioning of an array around a given value
Sort toys into three boxes, small, medium and big, in one pass along the shelf by swapping, without any extra boxes.
- Simple way
- time O(n)
room O(n) - Clever way
- time O(n)
room O(1)
The problem, simply
You get an array and a range [a, b]. Rearrange the array into three groups, in this order:
- numbers smaller than
a, - numbers from
atob(both ends included), - numbers bigger than
b.
The order inside each group does not matter. Do it in one pass and without a second array.
A story to remember it
Picture it
Eight numbers and the range 10 to 20. Green numbers are smaller than 10, blue numbers are inside 10 to 20, red numbers are bigger than 20.
- done
- rejected
- path
Examples
A normal case
- In
arr = [12, 3, 15, 25, 7, 18, 1, 30], range = [10, 20]- Out
[3, 1, 7, 12, 15, 18, 30, 25]
Small 3, 1, 7 first, then 12, 15, 18, then 30, 25. Any order inside each group is fine.
A longer case
- In
arr = [1, 14, 5, 20, 4, 2, 54, 20, 87, 98, 3, 1, 32], range = [14, 20]- Out
[1, 5, 4, 2, 1, 3, 14, 20, 20, 98, 87, 32, 54]
Six numbers below 14, three inside 14 to 20, and four above 20.
The range ends count as inside
- In
arr = [1, 2, 3, 3, 4], range = [1, 2]- Out
[1, 2, 3, 4, 3]
Nothing is smaller than 1. 1 and 2 are inside because the ends are included. 3, 4, 3 are bigger.
All in one group
- In
arr = [5, 5, 5], range = [1, 9]- Out
[5, 5, 5]
Everything is in the middle group. No swaps happen.
Way 1: the simple way
Use three bags. Walk the array and drop each number in the small, medium or big bag. Then pour the bags back in that order.
class Solution {
public void threeWayPartition(int[] arr, int a, int b) {
List<Integer> small = new ArrayList<>(), mid = new ArrayList<>(), big = new ArrayList<>();
for (int x : arr) {
if (x < a) small.add(x);
else if (x > b) big.add(x);
else mid.add(x);
}
int k = 0;
for (int x : small) arr[k++] = x;
for (int x : mid) arr[k++] = x;
for (int x : big) arr[k++] = x;
}
}How long it takes: two walks, O(n). Extra room: the bags hold all n numbers, O(n). Sorting the whole array would also work, but it costs O(n log n) and does more than needed.
Way 2: the clever way
Keep three fingers:
low: everything before it is small.mid: the number we look at now. Everything fromlowtomid - 1is medium.high: everything after it is big. Everything frommidtohighis not looked at yet.
While mid <= high:
- If
arr[mid] < a: swap it witharr[low], then move bothlowandmidright. - If
arr[mid] > b: swap it witharr[high]and movehighleft. Do not movemid: the number that came back is new. - Otherwise it is medium: move
midright.
Green is the small zone, blue the medium zone, red the big zone.
Three fingers, one pass
01 / 10
Range 10 to 20. low and mid start at the left, high at the right. Nothing is sorted into a zone yet.
- a
- 10
- b
- 20
- checking
- done
- answer
- rejected
- path
class Solution {
public void threeWayPartition(int[] arr, int a, int b) {
int low = 0, mid = 0, high = arr.length - 1;
while (mid <= high) {
if (arr[mid] < a) {
swap(arr, low, mid); // into the small zone
low++;
mid++;
} else if (arr[mid] > b) {
swap(arr, mid, high); // into the big zone
high--; // mid stays: the number that came back is new
} else {
mid++; // medium: already in the right zone
}
}
}
private void swap(int[] arr, int i, int j) {
int t = arr[i]; arr[i] = arr[j]; arr[j] = t;
}
}How long it takes: each step moves mid right or high left, so at most n steps, O(n). Extra room: three fingers, O(1).
Watch out for
- After a swap with
low, it is safe to movemid: the number that comes back fromlowwas already checked and is medium (or it is the same number). - The loop test is
mid <= high, notmid < high. The number athighitself is not checked yet. - The range ends are inside: use
< aand> b, not<=and>=.
What interviewers ask next
- Sort an array of 0s, 1s and 2s (LeetCode 75). This is the same loop with
a = b = 1. - Can you keep the order inside each group? Not with swaps in one pass. Use the three bags, which is a stable partition.
- Why does quicksort use a three-way partition? When many numbers equal the pivot, putting them all in the middle group means they are never sorted again. Two-way quicksort can slow down to
O(n^2)on many repeats. - Return where each group starts. After the loop, the small group is
0..low-1, the medium group islow..high, and the big group ishigh+1..n-1.
Remember this
Grown-up words: the three zones are a three-way partition, this loop is Dijkstra's Dutch national flag algorithm, and the zones staying correct at every step is the loop invariant.