Partitioning problems
Partitioning rearranges an array so that elements satisfying a condition come first. This lesson covers the Dutch national flag, partitioning by a pivot, alternating signs, stable versus unstable partitions and the three-way partition used by quicksort.
Learning goals
- Partition an array in place in one pass
- Maintain invariants with lo, mid and hi pointers
- Choose between stable (extra space) and unstable (in place) partitions
Prerequisites
Dutch national flag
Sort an array containing only 0, 1 and 2 in one pass and O(1) space, as if sorting the three stripes of the Dutch flag. Maintain three zones with pointers: [0, lo) holds 0s, [lo, mid) holds 1s, (hi, n) holds 2s, and [mid, hi] is unexamined. Dijkstra's solution handles each element once.
The first preset is exactly this problem, with pivot value 1.
1static void sortColors(int[] a) {2int lo = 0, mid = 0, hi = a.length - 1;3while (mid <= hi) {4if (a[mid] == 0) swap(a, lo++, mid++);5else if (a[mid] == 2) swap(a, mid, hi--); // do not advance mid: the swapped-in value is unexamined6else mid++;7}8}
Three-way partition (Dutch national flag)
Interactive · edit the inputint[] arr · length 6
Three zones grow from the edges toward the unclassified middle: less than 1, equal to 1, greater than 1.
1static void partition3(int[] a, int pivot) {2int lo = 0, mid = 0, hi = a.length - 1; // [0,lo) < pivot, [lo,mid) == pivot, (hi,n) > pivot3while (mid <= hi) {4if (a[mid] < pivot) swap(a, lo++, mid++);5else if (a[mid] > pivot) swap(a, mid, hi--);6else mid++;7}8}
Live state
lo
0
mid
0
hi
5
pivot
1
Execution trace (dry run table)Show 14 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | lo=0 mid=0 hi=5 pivot=1 | Three zones grow from the edges toward the unclassified middle: less than 1, equal to 1, greater than 1. |
| 2 | L4 | lo=0 mid=0 hi=5 a[mid]=2 | 2 > 1: swap it to the right end and shrink hi. Do not advance mid: the swapped-in value is unexamined. |
| 3 | L5 | lo=0 mid=0 hi=4 | Zones now: less [0,0), equal [0,0), greater (4,5]. |
| 4 | L4 | lo=0 mid=0 hi=4 a[mid]=0 | 0 < 1: swap it into the "less" zone and advance lo and mid. |
| 5 | L4 | lo=1 mid=1 hi=4 | Zones now: less [0,1), equal [1,1), greater (4,5]. |
| 6 | L4 | lo=1 mid=1 hi=4 a[mid]=0 | 0 < 1: swap it into the "less" zone and advance lo and mid. |
| 7 | L4 | lo=2 mid=2 hi=4 | Zones now: less [0,2), equal [2,2), greater (4,5]. |
| 8 | L4 | lo=2 mid=2 hi=4 a[mid]=2 | 2 > 1: swap it to the right end and shrink hi. Do not advance mid: the swapped-in value is unexamined. |
| 9 | L5 | lo=2 mid=2 hi=3 | Zones now: less [0,2), equal [2,2), greater (3,5]. |
| 10 | L4 | lo=2 mid=2 hi=3 a[mid]=1 | 1 equals the pivot: it is already in the middle zone, advance mid. |
| 11 | L6 | lo=2 mid=3 hi=3 | Zones now: less [0,2), equal [2,3), greater (3,5]. |
| 12 | L4 | lo=2 mid=3 hi=3 a[mid]=1 | 1 equals the pivot: it is already in the middle zone, advance mid. |
| 13 | L6 | lo=2 mid=4 hi=3 | Zones now: less [0,2), equal [2,4), greater (3,5]. |
| 14 | L8 | lo=2 mid=4 hi=3 swaps=2 | Partitioned in one pass with 2 swaps: 2 less, 2 equal, 2 greater. O(n) time, O(1) space. This is the partition step of 3-way quicksort. |
Partition by pivot
Partitioning by a pivot value moves every element smaller than the pivot to the left and the rest to the right. The Lomuto scheme keeps a store index of the next slot for a small value and swaps each small element into it. It returns how many values are smaller. One pass, O(n) time, O(1) space, not stable.
This is the heart of quicksort and quickselect.
1static int partition(int[] a, int pivot) { // returns the count of values < pivot2int store = 0;3for (int i = 0; i < a.length; i++) {4if (a[i] < pivot) { swap(a, store, i); store++; }5}6return store;7}
Partition by a pivot value (Lomuto style)
Interactive · edit the inputint[] arr · length 7
store marks where the next value smaller than 5 belongs; everything left of it is already smaller.
1static int partition(int[] a, int pivot) { // returns how many values are < pivot2int store = 0;3for (int i = 0; i < a.length; i++) {4if (a[i] < pivot) {5swap(a, store, i);6store++;7}8}9return store;10}
Live state
store
0
pivot
5
Execution trace (dry run table)Show 13 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | store=0 pivot=5 | store marks where the next value smaller than 5 belongs; everything left of it is already smaller. |
| 2 | L4 | i=0 store=0 a[i]=7 | 7 >= 5: leave it on the right. |
| 3 | L4 | i=1 store=0 a[i]=2 | 2 < 5: it belongs in the left part. |
| 4 | L6 | i=1 store=1 | Swapped into position 0; store = 1. |
| 5 | L4 | i=2 store=1 a[i]=9 | 9 >= 5: leave it on the right. |
| 6 | L4 | i=3 store=1 a[i]=4 | 4 < 5: it belongs in the left part. |
| 7 | L6 | i=3 store=2 | Swapped into position 1; store = 2. |
| 8 | L4 | i=4 store=2 a[i]=1 | 1 < 5: it belongs in the left part. |
| 9 | L6 | i=4 store=3 | Swapped into position 2; store = 3. |
| 10 | L4 | i=5 store=3 a[i]=8 | 8 >= 5: leave it on the right. |
| 11 | L4 | i=6 store=3 a[i]=3 | 3 < 5: it belongs in the left part. |
| 12 | L6 | i=6 store=4 | Swapped into position 3; store = 4. |
| 13 | L9 | return=4 swaps=4 | Return 4: the first 4 slots hold values < 5, the rest are >= 5. One pass, order inside each part is not preserved. |
Alternate signs
Rearranging so that signs alternate (positives at even indexes, negatives at odd) while keeping the relative order within each sign is easy with an output array and two write indexes advancing by 2. It needs equal numbers of positives and negatives (and no zeros) for a perfect alternation.
The order-preserving in-place variant requires rotations and costs O(n²).
1static int[] rearrange(int[] a) {2int[] out = new int[a.length];3int pos = 0, neg = 1;4for (int x : a) {5if (x > 0) { out[pos] = x; pos += 2; } else { out[neg] = x; neg += 2; }6}7return out;8}
Rearrange to alternate positive and negative
Interactive · edit the inputint[] arr · length 6
out[]
Positives fill the even indexes 0, 2, 4 ... and negatives the odd indexes 1, 3, 5 ... Each group keeps its original order.
1static int[] rearrange(int[] a) { // equal numbers of positives and negatives2int[] out = new int[a.length];3int pos = 0, neg = 1; // positives take even slots, negatives odd slots4for (int x : a) {5if (x > 0) { out[pos] = x; pos += 2; }6else { out[neg] = x; neg += 2; }7}8return out;9}
Live state
pos
0
neg
1
Execution trace (dry run table)Show 8 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L3 | pos=0 neg=1 | Positives fill the even indexes 0, 2, 4 ... and negatives the odd indexes 1, 3, 5 ... Each group keeps its original order. |
| 2 | L5 | i=0 x=3 pos=2 neg=1 | 3 is positive: out[0] = 3, next positive slot 2. |
| 3 | L5 | i=1 x=1 pos=4 neg=1 | 1 is positive: out[2] = 1, next positive slot 4. |
| 4 | L6 | i=2 x=-2 pos=4 neg=3 | -2 is negative: out[1] = -2, next negative slot 3. |
| 5 | L6 | i=3 x=-5 pos=4 neg=5 | -5 is negative: out[3] = -5, next negative slot 5. |
| 6 | L5 | i=4 x=2 pos=6 neg=5 | 2 is positive: out[4] = 2, next positive slot 6. |
| 7 | L6 | i=5 x=-4 pos=6 neg=7 | -4 is negative: out[5] = -4, next negative slot 7. |
| 8 | L8 | return=[3, -2, 1, -5, 2, -4] | Result [3, -2, 1, -5, 2, -4]: one pass, O(n) time and O(n) extra space. The in-place version is much harder because order within each sign is lost or costs shifts. |
Stable vs unstable partition
A stable partition keeps the original relative order inside each group; an unstable one may reorder equal-group elements. The stable version needs O(n) extra space (or O(n log n) time in place). The two-pointer version uses O(1) space but its long-distance swaps can reorder values.
Run both methods on the same duplicated input and compare the a/b tags.
| Method | Time | Extra space | Stable? |
|---|---|---|---|
| Copy evens then odds | O(n) | O(n) | yes |
| Two pointers swap | O(n) | O(1) | no |
| Stable in place (rotations) | O(n log n) | O(log n) | yes |
Stable vs unstable partition (evens first)
Interactive · edit the inputint[] arr · length 7
out[]
Pass 1 copies the even values in order, pass 2 copies the odd values in order.
1// stable: evens first, relative order kept, O(n) extra space2static int[] stablePartition(int[] a) {3int[] out = new int[a.length]; int w = 0;4for (int x : a) if (x % 2 == 0) out[w++] = x;5for (int x : a) if (x % 2 != 0) out[w++] = x;6return out;7}8// unstable: O(1) extra space, order not kept9static void unstablePartition(int[] a) {10int l = 0, r = a.length - 1;11while (l < r) {12if (a[l] % 2 == 0) l++;13else if (a[r] % 2 != 0) r--;14else swap(a, l++, r--);15}16}
Live state
Execution trace (dry run table)Show 16 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L3 | Pass 1 copies the even values in order, pass 2 copies the odd values in order. | |
| 2 | L4 | i=0 x=3 w=0 | 3 is odd: skip in this pass. |
| 3 | L4 | i=1 x=2 w=1 | 2 is even: copy to out[0]. |
| 4 | L4 | i=2 x=3 w=1 | 3 is odd: skip in this pass. |
| 5 | L4 | i=3 x=4 w=2 | 4 is even: copy to out[1]. |
| 6 | L4 | i=4 x=2 w=3 | 2 is even: copy to out[2]. |
| 7 | L4 | i=5 x=7 w=3 | 7 is odd: skip in this pass. |
| 8 | L4 | i=6 x=4 w=4 | 4 is even: copy to out[3]. |
| 9 | L5 | i=0 x=3 w=5 | 3 is odd: copy to out[4]. |
| 10 | L5 | i=1 x=2 w=5 | 2 is even: skip in this pass. |
| 11 | L5 | i=2 x=3 w=6 | 3 is odd: copy to out[5]. |
| 12 | L5 | i=3 x=4 w=6 | 4 is even: skip in this pass. |
| 13 | L5 | i=4 x=2 w=6 | 2 is even: skip in this pass. |
| 14 | L5 | i=5 x=7 w=7 | 7 is odd: copy to out[6]. |
| 15 | L5 | i=6 x=4 w=7 | 4 is even: skip in this pass. |
| 16 | L6 | return=[2, 4, 2, 4, 3, 3, 7] | Equal values keep their original a, b, c order: the partition is stable. Cost: an extra O(n) array. |
Three-way partition
A three-way partition splits around a pivot value into less, equal and greater. Handling the equal elements in one block is what makes three-way quicksort fast on inputs with many duplicates. It generalises the Dutch flag to an arbitrary pivot.
Choose a pivot that is not in the array, or one that appears many times, and watch the three zones.
1static void partition3(int[] a, int pivot) {2int lo = 0, mid = 0, hi = a.length - 1;3while (mid <= hi) {4if (a[mid] < pivot) swap(a, lo++, mid++);5else if (a[mid] > pivot) swap(a, mid, hi--);6else mid++;7}8}
Three-way partition (Dutch national flag)
Interactive · edit the inputint[] arr · length 6
Three zones grow from the edges toward the unclassified middle: less than 1, equal to 1, greater than 1.
1static void partition3(int[] a, int pivot) {2int lo = 0, mid = 0, hi = a.length - 1; // [0,lo) < pivot, [lo,mid) == pivot, (hi,n) > pivot3while (mid <= hi) {4if (a[mid] < pivot) swap(a, lo++, mid++);5else if (a[mid] > pivot) swap(a, mid, hi--);6else mid++;7}8}
Live state
lo
0
mid
0
hi
5
pivot
1
Execution trace (dry run table)Show 14 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | lo=0 mid=0 hi=5 pivot=1 | Three zones grow from the edges toward the unclassified middle: less than 1, equal to 1, greater than 1. |
| 2 | L4 | lo=0 mid=0 hi=5 a[mid]=2 | 2 > 1: swap it to the right end and shrink hi. Do not advance mid: the swapped-in value is unexamined. |
| 3 | L5 | lo=0 mid=0 hi=4 | Zones now: less [0,0), equal [0,0), greater (4,5]. |
| 4 | L4 | lo=0 mid=0 hi=4 a[mid]=0 | 0 < 1: swap it into the "less" zone and advance lo and mid. |
| 5 | L4 | lo=1 mid=1 hi=4 | Zones now: less [0,1), equal [1,1), greater (4,5]. |
| 6 | L4 | lo=1 mid=1 hi=4 a[mid]=0 | 0 < 1: swap it into the "less" zone and advance lo and mid. |
| 7 | L4 | lo=2 mid=2 hi=4 | Zones now: less [0,2), equal [2,2), greater (4,5]. |
| 8 | L4 | lo=2 mid=2 hi=4 a[mid]=2 | 2 > 1: swap it to the right end and shrink hi. Do not advance mid: the swapped-in value is unexamined. |
| 9 | L5 | lo=2 mid=2 hi=3 | Zones now: less [0,2), equal [2,2), greater (3,5]. |
| 10 | L4 | lo=2 mid=2 hi=3 a[mid]=1 | 1 equals the pivot: it is already in the middle zone, advance mid. |
| 11 | L6 | lo=2 mid=3 hi=3 | Zones now: less [0,2), equal [2,3), greater (3,5]. |
| 12 | L4 | lo=2 mid=3 hi=3 a[mid]=1 | 1 equals the pivot: it is already in the middle zone, advance mid. |
| 13 | L6 | lo=2 mid=4 hi=3 | Zones now: less [0,2), equal [2,4), greater (3,5]. |
| 14 | L8 | lo=2 mid=4 hi=3 swaps=2 | Partitioned in one pass with 2 swaps: 2 less, 2 equal, 2 greater. O(n) time, O(1) space. This is the partition step of 3-way quicksort. |
Practice ladder
Now try it yourself
Easy
- Sort {2, 0, 1, 2, 1, 0} with the Dutch flag method
- Partition {7, 2, 9, 4} around pivot 5
Medium
- Explain why mid is not advanced after swapping with hi
- Partition an array so evens come first, stably
Advanced
- Implement three-way quicksort and show it is O(n) on an array with only 3 distinct values
Key takeaways
- Partitioning is one pass with pointers that maintain zone invariants.
- Three zones (less, equal, greater) handle duplicates efficiently.
- Stable partitions cost extra space or time; unstable ones are in place.