Solution (source code)

= Solution

Use <three-way array partitioning> around a saved pivot value:

``
procedure Partition(var a; l, r; var m, n)
    x := a[Pivot(a,l,r)]
    m := l
    k := l
    n := r
    while k < n do
        if a[k] < x then
            swap(a[k], a[m])
            m := m+1
            k := k+1
        else if a[k] > x then
            n := n-1
            swap(a[k], a[n])
        else
            k := k+1
``

Assume a total ordering on the element keys. The <loop invariant> partitions the original segment into
$$
a[l..m)<x,\quad a[m..k)=x,\quad a[k..n)\text{ unclassified},\quad a[n..r)>x,
$$
with $l\leq m\leq k\leq n\leq r$ and the segment's <multiset> unchanged. A smaller entry exchanges with the first equality position; the displaced equal entry is accounted for when both indices advance. A greater entry exchanges with the last unclassified position, so `k` must stay to inspect the incoming entry. The <loop variant> $n-k$ decreases by one in every branch.

At termination the equality interval is $[m,n)$. It is nonempty because the pivot value came from the segment and swaps preserve the bag. The stronger strict outer-region conditions therefore imply the requested overlapping inequalities $a[l..n)\leq x\leq a[m..r)$ and $l\leq m<n\leq r$. Storing the pivot value avoids following an array position whose contents may move.

\Image[/past-exam-of-the-year-1-of-the-computer-science-course-of-the-university-of-oxford/2017/imperative-three-way-partition.png]
{title=Four-region invariant for duplicate-aware array partitioning}
{description=The unclassified interval shrinks at every comparison. On completion, the equal interval remains nonempty and both outer intervals can be discarded or searched by rank.}