← All Posts
DSA Series · Arrays · Dutch National Flag · Patterns

Dutch National Flag: Patterns

The three-way partition from the Dutch National Flag problem is not limited to sorting 0s, 1s, and 2s. The same pointer technique appears whenever you need to classify elements into three groups in a single pass. This post covers the most important patterns built on that idea.

Prerequisites: Make sure you understand the core DNF algorithm and its loop invariant before reading this page.

Pattern 1: Three-Way Quicksort Partition

Standard quicksort picks a pivot and partitions the array into two groups: elements less than the pivot and elements greater than the pivot. When the array contains many duplicate keys, this leads to poor performance because duplicates end up on one side and must be re-partitioned in subsequent recursive calls.

The fix is a three-way partition: elements less than the pivot go left, elements equal to the pivot stay in the middle, and elements greater go right. The middle group is already in its final position and is excluded from further recursion.

How It Maps to DNF

Replace the three categories (0, 1, 2) with comparisons against the pivot:

Invariant:
[0..low-1] = elements < pivot
[low..mid-1] = elements == pivot
[mid..high] = unexamined
[high+1..n-1] = elements > pivot

Step-Through Animation

Array: [4, 9, 4, 2, 7, 4, 1, 4, 8], pivot = 4. Watch elements less than 4 (green) move left, equal to 4 (gray) collect in the middle, and greater than 4 (red) move right.

< pivot == pivot > pivot low mid high
Press Step to begin.

C++ Implementation

#include <vector>
#include <algorithm>

// Three-way partition around pivot value.
// Returns [eqLow, eqHigh]: indices bounding the equal region.
std::pair<int,int> threeWayPartition(
        std::vector<int>& a, int lo, int hi, int pivot) {
    int low = lo, mid = lo, high = hi;
    while (mid <= high) {
        if (a[mid] < pivot) {
            std::swap(a[low], a[mid]);
            ++low; ++mid;
        } else if (a[mid] == pivot) {
            ++mid;
        } else {
            std::swap(a[mid], a[high]);
            --high;
        }
    }
    return {low, high};
}

// Quicksort using three-way partition.
void quickSort3Way(std::vector<int>& a, int lo, int hi) {
    if (lo >= hi) return;
    int pivot = a[lo + (hi - lo) / 2];
    auto [eqLo, eqHi] = threeWayPartition(a, lo, hi, pivot);
    quickSort3Way(a, lo, eqLo - 1);
    quickSort3Way(a, eqHi + 1, hi);
}
Why this matters: For an array where every element is the same, standard quicksort degrades to O(n2). Three-way partition finishes in O(n) because the entire array lands in the equal region on the first call. No further recursion is needed.

Complexity Comparison

ScenarioStandard Quicksort3-Way Quicksort
All distinct elementsO(n log n) avgO(n log n) avg
Many duplicatesO(n2) worstO(n log n) avg
All same elementsO(n2)O(n)

Pattern 2: Segregate Negatives, Zeros, and Positives

Given an array of integers, rearrange it so that all negative numbers come first, then all zeros, then all positive numbers. The relative order within each group does not need to be preserved (unstable partition).

This maps directly to the DNF pattern:

Step-Through Animation

Array: [3, -2, 0, -7, 5, 0, -1, 4]. Negatives (green) go left, zeros (gray) stay in the middle, positives (red) go right.

Negative Zero Positive
Press Step to begin.

C++ Implementation

#include <vector>
#include <algorithm>

void segregate(std::vector<int>& a) {
    int low = 0, mid = 0, high = (int)a.size() - 1;
    while (mid <= high) {
        if (a[mid] < 0) {
            std::swap(a[low], a[mid]);
            ++low; ++mid;
        } else if (a[mid] == 0) {
            ++mid;
        } else {
            std::swap(a[mid], a[high]);
            --high;
        }
    }
}

The logic is identical to the original DNF algorithm. The only change is the condition: instead of checking for 0, 1, 2, we check the sign of the element.

Stability note: This partition is not stable. If you need to preserve the relative order within each group, you need a different approach (such as a stable partition with O(n) extra space or a more complex in-place stable algorithm).

Walkthrough

mid=0: a[0]=3 is positive

Swap a[0] with a[7]. Array becomes [4, -2, 0, -7, 5, 0, -1, 3]. Decrement high to 6. Mid stays at 0 because the swapped-in value (4) is unexamined.

mid=0: a[0]=4 is positive

Swap a[0] with a[6]. Array becomes [-1, -2, 0, -7, 5, 0, 4, 3]. Decrement high to 5.

mid=0: a[0]=-1 is negative

Swap a[0] with a[0] (self-swap). Advance low to 1 and mid to 1. Array: [-1, -2, 0, -7, 5, 0, 4, 3].

mid=1: a[1]=-2 is negative

Swap a[1] with a[1]. Advance low to 2 and mid to 2. Array stays the same.

mid=2: a[2]=0 is zero

Zero belongs in the middle. Advance mid to 3. Array unchanged.

mid=3: a[3]=-7 is negative

Swap a[2] with a[3]. Array becomes [-1, -2, -7, 0, 5, 0, 4, 3]. Advance low to 3 and mid to 4.

mid=4: a[4]=5 is positive

Swap a[4] with a[5]. Array becomes [-1, -2, -7, 0, 0, 5, 4, 3]. Decrement high to 4.

mid=4: a[4]=0 is zero

Advance mid to 5. Now mid (5) > high (4), so the loop terminates. Final array: [-1, -2, -7, 0, 0, 5, 4, 3].

Pattern 3: Move All X to a Target Position

Given an array and a target value X, rearrange the array so that all occurrences of X are in a specific region (front, middle, or end) and all other elements fill the remaining positions. This is a two-way or three-way partition depending on the exact requirement.

Variant A: Move X to the End

Move all occurrences of a given value to the end of the array. This is a simplified two-pointer version of DNF (only two categories: "not X" and "X").

void moveToEnd(std::vector<int>& a, int x) {
    int low = 0, high = (int)a.size() - 1;
    while (low <= high) {
        if (a[low] != x) {
            ++low;
        } else {
            std::swap(a[low], a[high]);
            --high;
        }
    }
}

Here low scans forward and high marks the boundary of the "X region" growing from the right. When a[low] equals X, swap it to the back. When it does not, advance past it.

Variant B: Move X to the Front

Symmetric to variant A. Swap matching elements toward the beginning.

void moveToFront(std::vector<int>& a, int x) {
    int write = 0;
    for (int read = 0; read < (int)a.size(); ++read) {
        if (a[read] == x) {
            std::swap(a[write], a[read]);
            ++write;
        }
    }
}

Variant C: Three-Way with X in the Middle

Given values less than X, equal to X, and greater than X, place X in the middle. This is the full DNF pattern applied with X as the pivot.

void moveXToMiddle(std::vector<int>& a, int x) {
    int low = 0, mid = 0, high = (int)a.size() - 1;
    while (mid <= high) {
        if (a[mid] < x) {
            std::swap(a[low], a[mid]);
            ++low; ++mid;
        } else if (a[mid] == x) {
            ++mid;
        } else {
            std::swap(a[mid], a[high]);
            --high;
        }
    }
}

This is identical to the three-way quicksort partition from Pattern 1. The only difference is the caller's intent: here we want X specifically in the middle rather than partitioning for a recursive sort.

Step-Through Animation

Array: [5, 3, 3, 7, 3, 9, 1, 3], target X = 3. Move all 3s to the end.

Not X (keep) X (move to end)
Press Step to begin.

When to Recognize the DNF Pattern

Use the three-way partition whenever a problem asks you to classify array elements into exactly two or three groups in a single pass. Look for these signals:

Two-Way (2 categories)

Use two pointers (low and high). One category goes left, the other goes right. Examples: move zeros to end, separate even/odd, partition around pivot in standard quicksort.

Three-Way (3 categories)

Use three pointers (low, mid, high). One category left, one middle, one right. Examples: sort colors, segregate negatives/zeros/positives, 3-way quicksort partition.

Practice Problems

ProblemPlatformPatternDifficulty
Sort Colors (LC 75)LeetCodeDirect DNFMedium
Move Zeroes (LC 283)LeetCodeTwo-way partitionEasy
Sort Array by Parity (LC 905)LeetCodeTwo-way partitionEasy
Sort Array by Parity II (LC 922)LeetCodeTwo-pointer interleaveEasy
Partition Array (LC 2161)LeetCodeThree-way (pivot)Medium
Wiggle Sort II (LC 324)LeetCodeDNF + virtual indexingMedium
3-Way String PartitionCodeforcesDNF on charactersMedium
Segregate 0s and 1sGFGTwo-way partitionEasy
Segregate Neg/Zero/PosGFGDirect DNFEasy

Key Takeaways